├── android_tools ├── bradb.exe └── adb ├── TouchServer ├── app │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── hlq │ │ │ └── touchserver │ │ │ ├── LogUtil.java │ │ │ ├── wrappers │ │ │ ├── DisplayManager.java │ │ │ ├── PowerManager.java │ │ │ ├── InputManager.java │ │ │ └── ServiceManager.java │ │ │ ├── EventInjector.java │ │ │ └── TouchEventServer.java │ ├── build.gradle │ └── proguard-rules.pro ├── settings.gradle ├── .gitignore ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── build.gradle ├── gradle.properties ├── gradlew.bat └── gradlew ├── settings.gradle ├── config.properties ├── libs └── ddmlib.jar ├── phone-mirror.jar ├── screenshot └── art.png ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src └── main │ ├── resources │ ├── minicapLib.zip │ └── singletouch.apk │ └── java │ └── com │ └── hlq │ └── mobile │ ├── Main.java │ ├── utils │ ├── Log.java │ └── Tools.java │ ├── adb │ ├── DeviceChangeListener.java │ └── AndroidBridge.java │ ├── view │ ├── ImageJPanel.java │ ├── DeviceItem.java │ ├── Mirror.java │ └── VFlowLayout.java │ ├── Application.java │ └── mini │ ├── SingleTouch.java │ └── MinicapReceiver.java ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /android_tools/bradb.exe: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /TouchServer/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /TouchServer/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'phone-mirror' 2 | 3 | -------------------------------------------------------------------------------- /config.properties: -------------------------------------------------------------------------------- 1 | #adb.path=D:\\dev\\android_sdk\\platform-tools\\adb -------------------------------------------------------------------------------- /libs/ddmlib.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/libs/ddmlib.jar -------------------------------------------------------------------------------- /phone-mirror.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/phone-mirror.jar -------------------------------------------------------------------------------- /android_tools/adb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/android_tools/adb -------------------------------------------------------------------------------- /screenshot/art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/screenshot/art.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.gradle 2 | /.idea 3 | /build 4 | /out 5 | local.properties 6 | /phoneMirrorLibs 7 | .DS_Store 8 | /src/.DS_Store -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/minicapLib.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/src/main/resources/minicapLib.zip -------------------------------------------------------------------------------- /src/main/resources/singletouch.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/src/main/resources/singletouch.apk -------------------------------------------------------------------------------- /TouchServer/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /TouchServer/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/huanglqweiwei/PhoneMirror/HEAD/TouchServer/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/Main.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile; 2 | 3 | 4 | 5 | 6 | public class Main { 7 | public static void main(String[] args){ 8 | new Application().start(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/utils/Log.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.utils; 2 | 3 | public class Log { 4 | public static void d(String tag,String msg){ 5 | System.out.println(tag + " : " + msg); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/adb/DeviceChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.adb; 2 | 3 | import com.android.ddmlib.IDevice; 4 | 5 | public interface DeviceChangeListener { 6 | void onDeviceConnect(IDevice iDevice); 7 | void onDeviceDisconnect(IDevice iDevice); 8 | } 9 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Jul 18 17:49:23 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip 7 | -------------------------------------------------------------------------------- /TouchServer/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 05 16:21:38 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/java/com/hlq/touchserver/LogUtil.java: -------------------------------------------------------------------------------- 1 | package com.hlq.touchserver; 2 | 3 | import android.util.Log; 4 | 5 | public class LogUtil { 6 | private static final String TAG = "TouchServer"; 7 | 8 | public static void d(String tag, String msg) { 9 | System.out.println(TAG + "_" + tag + " : " + msg); 10 | Log.w(TAG + "_" + tag, msg); 11 | } 12 | 13 | public static void d(String msg) { 14 | System.out.println(TAG + " : " + msg); 15 | Log.w(TAG, msg); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PhoneMirror 2 | Android手机投屏到PC,并可通过PC控制手机的java桌面工具,最低支持java6,支持同时控制多台手机。 3 | ## 应用截图 4 | ![应用截图](./screenshot/art.png) 5 | ## 使用方法 6 | * 手机连接电脑,并开启`开发者选项->USB调试`; 7 | > 注意:
8 | > 小米手机需要同时开启`USB调试(安全设置)`才能处理点击事件。 9 | * 运行项目目录下的`phone-mirror.jar` 10 | ``` 11 | java -jar ./phone-mirror.jar 12 | ``` 13 | 14 | >项目运行所依赖的adb工具支持手动配置,配置方法:
15 | >`phone-mirror.jar`同目录下创建`config.properties`文件,配置属性:`adb.path=your adb path`。 16 | ## 说明 17 | * 手机画面同步使用了[minicap](https://github.com/openstf/minicap)库; 18 | * 点击事件处理使用项目目录下的android工程`TouchServer`。 19 | 20 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/view/ImageJPanel.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.view; 2 | 3 | import javax.swing.*; 4 | import java.awt.*; 5 | 6 | public class ImageJPanel extends JPanel { 7 | private Image mImage; 8 | 9 | public void setImage(Image mImage) { 10 | this.mImage = mImage; 11 | repaint(); 12 | } 13 | 14 | @Override 15 | protected void paintComponent(Graphics g) { 16 | if (mImage == null) { 17 | super.paintComponent(g); 18 | } else { 19 | g.drawImage(mImage,0,0,getWidth(),getHeight(),this); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /TouchServer/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.hlq.touchserver" 7 | minSdkVersion 19 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | buildTypes { 13 | release { 14 | minifyEnabled false 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | } 19 | 20 | dependencies { 21 | implementation fileTree(dir: 'libs', include: ['*.jar']) 22 | } 23 | -------------------------------------------------------------------------------- /TouchServer/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.2.0' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /TouchServer/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /TouchServer/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/java/com/hlq/touchserver/wrappers/DisplayManager.java: -------------------------------------------------------------------------------- 1 | package com.hlq.touchserver.wrappers; 2 | 3 | 4 | import android.os.IInterface; 5 | 6 | public final class DisplayManager { 7 | private final IInterface manager; 8 | 9 | public DisplayManager(IInterface manager) { 10 | this.manager = manager; 11 | } 12 | 13 | public int[] getDisplayInfo() { 14 | try { 15 | Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, 0); 16 | Class cls = displayInfo.getClass(); 17 | // width and height already take the rotation into account 18 | int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo); 19 | int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo); 20 | int rotation = cls.getDeclaredField("rotation").getInt(displayInfo); 21 | return new int[]{width,height,rotation}; 22 | } catch (Exception e) { 23 | throw new AssertionError(e); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/java/com/hlq/touchserver/wrappers/PowerManager.java: -------------------------------------------------------------------------------- 1 | package com.hlq.touchserver.wrappers; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.Build; 5 | import android.os.IInterface; 6 | 7 | import java.lang.reflect.InvocationTargetException; 8 | import java.lang.reflect.Method; 9 | 10 | public final class PowerManager { 11 | private final IInterface manager; 12 | private final Method isScreenOnMethod; 13 | 14 | public PowerManager(IInterface manager) { 15 | this.manager = manager; 16 | try { 17 | @SuppressLint("ObsoleteSdkInt") // we may lower minSdkVersion in the future 18 | String methodName = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH ? "isInteractive" : "isScreenOn"; 19 | isScreenOnMethod = manager.getClass().getMethod(methodName); 20 | } catch (NoSuchMethodException e) { 21 | throw new AssertionError(e); 22 | } 23 | } 24 | 25 | public boolean isScreenOn() { 26 | try { 27 | return (Boolean) isScreenOnMethod.invoke(manager); 28 | } catch (Exception e) { 29 | e.printStackTrace(); 30 | } 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/java/com/hlq/touchserver/wrappers/InputManager.java: -------------------------------------------------------------------------------- 1 | package com.hlq.touchserver.wrappers; 2 | 3 | import android.os.IInterface; 4 | import android.view.InputEvent; 5 | 6 | import java.lang.reflect.InvocationTargetException; 7 | import java.lang.reflect.Method; 8 | 9 | public final class InputManager { 10 | 11 | public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0; 12 | public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT = 1; 13 | public static final int INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH = 2; 14 | 15 | private final IInterface manager; 16 | private final Method injectInputEventMethod; 17 | 18 | public InputManager(IInterface manager) { 19 | this.manager = manager; 20 | try { 21 | injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class); 22 | } catch (NoSuchMethodException e) { 23 | throw new AssertionError(e); 24 | } 25 | } 26 | 27 | public boolean injectInputEvent(InputEvent inputEvent, int mode) { 28 | try { 29 | return (Boolean) injectInputEventMethod.invoke(manager, inputEvent, mode); 30 | } catch (InvocationTargetException | IllegalAccessException e) { 31 | throw new AssertionError(e); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/view/DeviceItem.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.view; 2 | 3 | import com.android.ddmlib.IDevice; 4 | import com.hlq.mobile.utils.Log; 5 | 6 | import javax.swing.*; 7 | import java.awt.event.ActionEvent; 8 | 9 | public class DeviceItem { 10 | private static final String TAG = "DeviceItem"; 11 | private IDevice mIDevice; 12 | private JFrame mJFrame; 13 | private JButton mJButton; 14 | private Mirror mMirror; 15 | private static int COUNT = 0; 16 | private int index; 17 | 18 | public DeviceItem(IDevice iDevice, JFrame jFrame) { 19 | mIDevice = iDevice; 20 | mJFrame = jFrame; 21 | COUNT++; 22 | index = COUNT; 23 | } 24 | 25 | 26 | public void setDevice(IDevice iDevice) { 27 | mIDevice = iDevice; 28 | } 29 | 30 | public JButton getJButton() { 31 | if (mJButton == null) { 32 | mJButton = new JButton(mIDevice.getName()); 33 | mJButton.setSize(288,40); 34 | mJButton.addActionListener(new AbstractAction() { 35 | @Override 36 | public void actionPerformed(ActionEvent e) { 37 | Log.d(TAG,"actionPerformed"); 38 | if (mMirror == null) { 39 | mMirror = new Mirror(mIDevice,mJFrame,index); 40 | } 41 | mMirror.show(mIDevice); 42 | } 43 | }); 44 | } 45 | return mJButton; 46 | } 47 | public void stop(){ 48 | if (mMirror != null) { 49 | mMirror.stop(); 50 | mMirror = null; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/adb/AndroidBridge.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.adb; 2 | 3 | import com.android.ddmlib.AndroidDebugBridge; 4 | import com.android.ddmlib.IDevice; 5 | import com.hlq.mobile.utils.Log; 6 | 7 | public class AndroidBridge implements AndroidDebugBridge.IDeviceChangeListener { 8 | private static final String TAG = "AndroidBridge"; 9 | public static String sAdbPath; 10 | private DeviceChangeListener mChangeListener; 11 | 12 | private AndroidBridge(String adbPath){ 13 | AndroidDebugBridge.init(false); 14 | AndroidDebugBridge.addDeviceChangeListener(this); 15 | AndroidDebugBridge.createBridge(adbPath, true); 16 | } 17 | 18 | 19 | public static AndroidBridge init(String adbPath){ 20 | sAdbPath = adbPath; 21 | return new AndroidBridge(adbPath); 22 | } 23 | 24 | public static void terminate() { 25 | AndroidDebugBridge.disconnectBridge(); 26 | AndroidDebugBridge.terminate(); 27 | 28 | } 29 | 30 | @Override 31 | public void deviceConnected(IDevice iDevice) { 32 | 33 | 34 | } 35 | 36 | @Override 37 | public void deviceDisconnected(IDevice iDevice) { 38 | if (mChangeListener != null) { 39 | mChangeListener.onDeviceDisconnect(iDevice); 40 | } 41 | } 42 | 43 | @Override 44 | public void deviceChanged(IDevice iDevice, int mark) { 45 | Log.d(TAG,"deviceChanged : " + iDevice); 46 | if (mark == IDevice.CHANGE_BUILD_INFO) { 47 | if (mChangeListener != null) { 48 | mChangeListener.onDeviceConnect(iDevice); 49 | } 50 | } 51 | } 52 | 53 | public void setChangeListener(DeviceChangeListener changeListener) { 54 | mChangeListener = changeListener; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/java/com/hlq/touchserver/wrappers/ServiceManager.java: -------------------------------------------------------------------------------- 1 | package com.hlq.touchserver.wrappers; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.os.IBinder; 5 | import android.os.IInterface; 6 | 7 | import java.lang.reflect.Method; 8 | 9 | @SuppressLint("PrivateApi") 10 | public final class ServiceManager { 11 | private final Method getServiceMethod; 12 | 13 | private DisplayManager displayManager; 14 | private InputManager inputManager; 15 | private PowerManager powerManager; 16 | 17 | public ServiceManager() { 18 | try { 19 | getServiceMethod = Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class); 20 | } catch (Exception e) { 21 | throw new AssertionError(e); 22 | } 23 | } 24 | 25 | private IInterface getService(String service, String type) { 26 | try { 27 | IBinder binder = (IBinder) getServiceMethod.invoke(null, service); 28 | Method asInterfaceMethod = Class.forName(type + "$Stub").getMethod("asInterface", IBinder.class); 29 | return (IInterface) asInterfaceMethod.invoke(null, binder); 30 | } catch (Exception e) { 31 | throw new AssertionError(e); 32 | } 33 | } 34 | 35 | 36 | public DisplayManager getDisplayManager() { 37 | if (displayManager == null) { 38 | displayManager = new DisplayManager(getService("display", "android.hardware.display.IDisplayManager")); 39 | } 40 | return displayManager; 41 | } 42 | 43 | public InputManager getInputManager() { 44 | if (inputManager == null) { 45 | inputManager = new InputManager(getService("input", "android.hardware.input.IInputManager")); 46 | } 47 | return inputManager; 48 | } 49 | 50 | public PowerManager getPowerManager() { 51 | if (powerManager == null) { 52 | powerManager = new PowerManager(getService("power", "android.os.IPowerManager")); 53 | } 54 | return powerManager; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /TouchServer/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/java/com/hlq/touchserver/EventInjector.java: -------------------------------------------------------------------------------- 1 | package com.hlq.touchserver; 2 | 3 | import android.os.SystemClock; 4 | import android.view.InputDevice; 5 | import android.view.KeyCharacterMap; 6 | import android.view.KeyEvent; 7 | import android.view.MotionEvent; 8 | import com.hlq.touchserver.wrappers.InputManager; 9 | import com.hlq.touchserver.wrappers.PowerManager; 10 | 11 | public class EventInjector { 12 | private InputManager mInputManager; 13 | private PowerManager mPowerManager; 14 | private long mLastTime; 15 | private final MotionEvent.PointerProperties[] pointerProperties = {new MotionEvent.PointerProperties()}; 16 | private final MotionEvent.PointerCoords[] pointerCoords = {new MotionEvent.PointerCoords()}; 17 | 18 | public EventInjector(InputManager inputManager, PowerManager powerManager) { 19 | mInputManager = inputManager; 20 | this.mPowerManager = powerManager; 21 | 22 | MotionEvent.PointerProperties props = pointerProperties[0]; 23 | props.id = 0; 24 | props.toolType = MotionEvent.TOOL_TYPE_FINGER; 25 | 26 | MotionEvent.PointerCoords coords = pointerCoords[0]; 27 | coords.orientation = 0; 28 | coords.pressure = 1; 29 | coords.size = 1; 30 | } 31 | 32 | private void setPointerCoords(int x, int y) { 33 | MotionEvent.PointerCoords coords = pointerCoords[0]; 34 | coords.x = x; 35 | coords.y = y; 36 | } 37 | 38 | void injectInputEvent(int action, int x, int y) { 39 | long now = SystemClock.uptimeMillis(); 40 | if (action == MotionEvent.ACTION_DOWN) { 41 | if (!mPowerManager.isScreenOn()) { 42 | injectKeycode(KeyEvent.KEYCODE_POWER); 43 | return; 44 | } 45 | mLastTime = now; 46 | } 47 | if (x > 0) { 48 | setPointerCoords(x, y); 49 | } 50 | MotionEvent event = MotionEvent.obtain(mLastTime, now, action, 1, pointerProperties, pointerCoords, 0, MotionEvent.BUTTON_PRIMARY, 1f, 1f, 0, 0, InputDevice.SOURCE_TOUCHSCREEN, 0); 51 | mInputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 52 | } 53 | 54 | boolean injectKeycode(int keyCode) { 55 | return injectKeyEvent(KeyEvent.ACTION_DOWN, keyCode) 56 | && injectKeyEvent(KeyEvent.ACTION_UP, keyCode); 57 | } 58 | 59 | private boolean injectKeyEvent(int action, int keyCode) { 60 | long now = SystemClock.uptimeMillis(); 61 | KeyEvent event = new KeyEvent(now, now, action, keyCode, 0, 0, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0, 62 | InputDevice.SOURCE_KEYBOARD); 63 | return mInputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 64 | } 65 | 66 | private void setScroll(float hScroll, float vScroll) { 67 | MotionEvent.PointerCoords coords = pointerCoords[0]; 68 | coords.setAxisValue(MotionEvent.AXIS_HSCROLL, hScroll); 69 | coords.setAxisValue(MotionEvent.AXIS_VSCROLL, vScroll); 70 | } 71 | 72 | boolean injectScroll(int x, int y, float hScroll, float vScroll) { 73 | 74 | setPointerCoords(x,y); 75 | setScroll(hScroll, vScroll); 76 | MotionEvent event = MotionEvent.obtain(mLastTime, SystemClock.uptimeMillis(), MotionEvent.ACTION_SCROLL, 1, pointerProperties, pointerCoords, 0, 0, 1f, 1f, 0, 77 | 0, InputDevice.SOURCE_MOUSE, 0); 78 | return mInputManager.injectInputEvent(event, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/Application.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile; 2 | 3 | 4 | import com.android.ddmlib.IDevice; 5 | import com.hlq.mobile.adb.AndroidBridge; 6 | import com.hlq.mobile.adb.DeviceChangeListener; 7 | import com.hlq.mobile.utils.Log; 8 | import com.hlq.mobile.utils.Tools; 9 | import com.hlq.mobile.view.DeviceItem; 10 | import com.hlq.mobile.view.VFlowLayout; 11 | 12 | import javax.swing.*; 13 | import java.awt.*; 14 | import java.awt.event.WindowEvent; 15 | import java.awt.event.WindowListener; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | public class Application implements WindowListener, DeviceChangeListener { 20 | private static final String TAG = "Application"; 21 | private AndroidBridge mAndroidBridge; 22 | private JFrame mJFrame; 23 | private Map mDeviceItems; 24 | private JPanel mJPanel; 25 | 26 | public void start() { 27 | 28 | mJFrame = new JFrame("PhoneMirror"); 29 | 30 | mJFrame.setSize(290, 512); 31 | mJFrame.setLocationRelativeTo(null); 32 | mJFrame.addWindowListener(this); 33 | mJFrame.setBackground(Color.WHITE); 34 | 35 | mJPanel = new JPanel(); 36 | mJPanel.setLayout(new VFlowLayout()); 37 | 38 | mJFrame.add(new JScrollPane(mJPanel), BorderLayout.CENTER); 39 | 40 | mJFrame.setVisible(true); 41 | String adbPath = Tools.getAdbPath(); 42 | Log.d(TAG,"adb Path = " + adbPath); 43 | mAndroidBridge = AndroidBridge.init(adbPath); 44 | mAndroidBridge.setChangeListener(this); 45 | 46 | } 47 | 48 | 49 | @Override 50 | public void windowOpened(WindowEvent e) { 51 | Log.d(TAG, "windowOpened"); 52 | } 53 | 54 | @Override 55 | public void windowClosing(WindowEvent e) { 56 | Log.d(TAG, "windowClosing"); 57 | if (mDeviceItems != null) { 58 | for (DeviceItem item:mDeviceItems.values()) { 59 | item.stop(); 60 | } 61 | mDeviceItems.clear(); 62 | } 63 | System.exit(0); 64 | } 65 | 66 | @Override 67 | public void windowClosed(WindowEvent e) { 68 | } 69 | 70 | @Override 71 | public void windowIconified(WindowEvent e) { 72 | } 73 | 74 | @Override 75 | public void windowDeiconified(WindowEvent e) { 76 | } 77 | 78 | @Override 79 | public void windowActivated(WindowEvent e) { 80 | } 81 | 82 | @Override 83 | public void windowDeactivated(WindowEvent e) { 84 | } 85 | 86 | @Override 87 | public void onDeviceConnect(IDevice iDevice) { 88 | Log.d(TAG, "deviceConnected : " + iDevice); 89 | if (mDeviceItems == null) { 90 | mDeviceItems = new HashMap(); 91 | } 92 | DeviceItem deviceItem = mDeviceItems.get(iDevice.getSerialNumber()); 93 | if (deviceItem == null) { 94 | deviceItem = new DeviceItem(iDevice,mJFrame); 95 | mDeviceItems.put(iDevice.getSerialNumber(), deviceItem); 96 | } else { 97 | deviceItem.setDevice(iDevice); 98 | } 99 | 100 | addItem(deviceItem.getJButton()); 101 | } 102 | 103 | private void addItem(JButton jButton) { 104 | Component[] components = mJPanel.getComponents(); 105 | boolean contain = false; 106 | if (components != null) { 107 | for (Component component : components) { 108 | if (component.equals(jButton)) { 109 | contain = true; 110 | break; 111 | } 112 | } 113 | } 114 | if (!contain) { 115 | try { 116 | Log.d(TAG, "add device"); 117 | mJPanel.add(jButton); 118 | SwingUtilities.updateComponentTreeUI(mJPanel); 119 | } catch (Exception e) { 120 | e.printStackTrace(); 121 | } 122 | } 123 | } 124 | 125 | 126 | @Override 127 | public void onDeviceDisconnect(IDevice iDevice) { 128 | Log.d(TAG, "deviceDisconnected : " + iDevice); 129 | if (mDeviceItems != null) { 130 | DeviceItem deviceItem = mDeviceItems.get(iDevice.getSerialNumber()); 131 | if (deviceItem != null) { 132 | deviceItem.stop(); 133 | mJPanel.remove(deviceItem.getJButton()); 134 | SwingUtilities.updateComponentTreeUI(mJPanel); 135 | } 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /TouchServer/app/src/main/java/com/hlq/touchserver/TouchEventServer.java: -------------------------------------------------------------------------------- 1 | package com.hlq.touchserver; 2 | 3 | import android.net.LocalServerSocket; 4 | import android.net.LocalSocket; 5 | import android.util.Log; 6 | import android.view.MotionEvent; 7 | import com.hlq.touchserver.wrappers.ServiceManager; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.io.OutputStream; 12 | import java.nio.ByteBuffer; 13 | 14 | public class TouchEventServer { 15 | private static final String TAG = "TouchEventServer"; 16 | private static final String HOST = "singleTouch"; 17 | private static final byte TYPE_MOTION = 0; 18 | private static final byte TYPE_KEYCODE = 1; 19 | private final ServiceManager mServiceManager; 20 | private final byte[] lenBytes = new byte[4]; 21 | private final byte[] contentBytes = new byte[18]; 22 | private final InputStream mInputStream; 23 | private EventInjector mEventInjector; 24 | 25 | public static void main(String[] args){ 26 | try { 27 | new TouchEventServer().loop(); 28 | } catch (IOException e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | private TouchEventServer() throws IOException{ 34 | LocalServerSocket server = null; 35 | try { 36 | server = new LocalServerSocket(HOST); 37 | LogUtil.d("OK !"); 38 | LocalSocket client = server.accept(); 39 | LogUtil.d("client bind SUCCESS !"); 40 | mServiceManager = new ServiceManager(); 41 | int[] info = mServiceManager.getDisplayManager().getDisplayInfo(); 42 | OutputStream output = client.getOutputStream(); 43 | output.write(( 44 | "width : " + info[0] + '\n' 45 | +"height : " + info[1] + '\n' 46 | +"rotation : " + info[2] 47 | ).getBytes()); 48 | mInputStream = client.getInputStream(); 49 | } catch (IOException e) { 50 | Log.e(TAG, "TouchEventServer: ",e ); 51 | throw e; 52 | } finally { 53 | if (server != null) { 54 | try { 55 | server.close(); 56 | } catch (IOException e) { 57 | e.printStackTrace(); 58 | } 59 | } 60 | } 61 | } 62 | private ByteBuffer readByte(byte[] bytes,int length) { 63 | if (length > bytes.length) { 64 | bytes = new byte[length]; 65 | } 66 | try { 67 | int len; 68 | int offset = 0; 69 | while (offset != length && (len = mInputStream.read(bytes,offset,length - offset)) > -1 ){ 70 | offset += len; 71 | } 72 | if (offset == length) { 73 | return ByteBuffer.wrap(bytes,0,offset); 74 | } 75 | } catch (Exception e) { 76 | e.printStackTrace(); 77 | } 78 | return null; 79 | } 80 | 81 | private void loop(){ 82 | while (true){ 83 | ByteBuffer lenBuffer = readByte(lenBytes,lenBytes.length); 84 | if (lenBuffer != null) { 85 | int len = lenBuffer.getInt(); 86 | if (len > 0) { 87 | ByteBuffer buffer = readByte(contentBytes, len); 88 | if (buffer != null) { 89 | try { 90 | handleEvent(buffer); 91 | }catch (Exception e){ 92 | e.printStackTrace(); 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | private void handleEvent(ByteBuffer buffer) { 101 | if (mEventInjector == null) { 102 | mEventInjector = new EventInjector(mServiceManager.getInputManager(),mServiceManager.getPowerManager()); 103 | } 104 | byte type = buffer.get(); 105 | if (type == TYPE_MOTION) { 106 | byte action = buffer.get(); 107 | switch (action) { 108 | case MotionEvent.ACTION_DOWN: 109 | case MotionEvent.ACTION_MOVE: 110 | mEventInjector.injectInputEvent(action,buffer.getInt(),buffer.getInt()); 111 | break; 112 | case MotionEvent.ACTION_UP: 113 | mEventInjector.injectInputEvent(action,-1,-1); 114 | break; 115 | case MotionEvent.ACTION_SCROLL: 116 | mEventInjector.injectScroll(buffer.getInt(),buffer.getInt(),buffer.getFloat(),buffer.getFloat()); 117 | break; 118 | } 119 | } else if (type == TYPE_KEYCODE){ 120 | mEventInjector.injectKeycode(buffer.getInt()); 121 | } 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /TouchServer/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/mini/SingleTouch.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.mini; 2 | 3 | import com.android.ddmlib.IDevice; 4 | import com.hlq.mobile.adb.AndroidBridge; 5 | import com.hlq.mobile.utils.Log; 6 | import com.hlq.mobile.utils.Tools; 7 | 8 | import java.io.*; 9 | import java.net.InetSocketAddress; 10 | import java.nio.ByteBuffer; 11 | import java.nio.channels.SocketChannel; 12 | import java.util.concurrent.Semaphore; 13 | 14 | public class SingleTouch implements Runnable{ 15 | private static final String TAG = "SingleTouch"; 16 | private Process mProcess; 17 | private ByteBuffer mBuffer = ByteBuffer.allocate(22); 18 | private static final byte TYPE_MOTION = 0; 19 | private static final byte TYPE_KEYCODE = 1; 20 | private static final byte ACTION_DOWN = 0; 21 | private static final byte ACTION_UP = 1; 22 | private static final byte ACTION_MOVE = 2; 23 | private static final byte ACTION_SCROLL = 8; 24 | private boolean mStopped = false; 25 | private int mPort; 26 | private SocketChannel mChannel; 27 | private IDevice mDevice; 28 | 29 | public void start(IDevice device, int port) { 30 | mPort = port; 31 | mDevice = device; 32 | Tools.sExecutor.submit(this); 33 | mStopped = false; 34 | } 35 | 36 | public void stop() { 37 | if (!mStopped) { 38 | mStopped = true; 39 | Log.d(TAG,"stop"); 40 | if (mProcess != null) { 41 | mProcess.destroy(); 42 | mProcess = null; 43 | } 44 | if (mDevice.isOnline()) { 45 | Tools.executeShellCommand(mDevice, "rm /data/local/tmp/" + Tools.SINGLETOUCH_APK); 46 | } 47 | } 48 | } 49 | 50 | @Override 51 | public void run() { 52 | Semaphore semaphore = new Semaphore(1); 53 | try { 54 | semaphore.acquire(); 55 | } catch (InterruptedException e) { 56 | e.printStackTrace(); 57 | } 58 | if (prepare(semaphore)) { 59 | } 60 | try { 61 | semaphore.acquire(); 62 | } catch (InterruptedException e) { 63 | e.printStackTrace(); 64 | } 65 | createSocket(); 66 | } 67 | 68 | protected void createSocket() { 69 | try { 70 | mChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", mPort)); 71 | mChannel.socket().setKeepAlive(true); 72 | mChannel.socket().setTcpNoDelay(true); 73 | if (mChannel.finishConnect()) { 74 | ByteBuffer buffer = ByteBuffer.allocate(40); 75 | int len = mChannel.read(buffer); 76 | Log.d(TAG, "read len = " + len); 77 | buffer.flip(); 78 | Log.d(TAG, "received : \n" + new String(buffer.array(), 0, len, "utf-8")); 79 | } 80 | } catch (IOException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | 85 | public boolean prepare(Semaphore semaphore) { 86 | BufferedReader reader = null; 87 | try { 88 | File apkFile = new File(Tools.getLocalPath() + File.separator + Tools.LIBS_PATH_NAME, Tools.SINGLETOUCH_APK); 89 | if (!apkFile.exists()) { 90 | Tools.copySingleTouchApk(apkFile); 91 | } 92 | String remotePath = "/data/local/tmp/" + Tools.SINGLETOUCH_APK; 93 | mDevice.pushFile(apkFile.getAbsolutePath(),remotePath); 94 | mDevice.createForward(mPort, "singleTouch", IDevice.DeviceUnixSocketNamespace.ABSTRACT); 95 | mProcess = Runtime.getRuntime().exec(AndroidBridge.sAdbPath + " -s " + mDevice.getSerialNumber() + " shell"); 96 | OutputStream output = mProcess.getOutputStream(); 97 | output.write(("export CLASSPATH=" + remotePath + "\n").getBytes()); 98 | output.write(("app_process /system/bin com.hlq.touchserver.TouchEventServer\n").getBytes()); 99 | output.flush(); 100 | reader = new BufferedReader(new InputStreamReader(new SequenceInputStream(mProcess.getInputStream(), mProcess.getErrorStream()))); 101 | String line; 102 | while ((line = reader.readLine()) != null) { 103 | Log.d(TAG, "line = " + line); 104 | if (line.contains("OK")) { 105 | break; 106 | } 107 | } 108 | return true; 109 | } catch (Exception e) { 110 | e.printStackTrace(); 111 | } finally { 112 | if (reader != null) { 113 | try { 114 | reader.close(); 115 | } catch (IOException e) { 116 | e.printStackTrace(); 117 | } 118 | } 119 | semaphore.release(); 120 | } 121 | return false; 122 | } 123 | 124 | 125 | public void touchDown(int x, int y) { 126 | sendTouch(x, y, ACTION_DOWN); 127 | 128 | } 129 | 130 | public void touchMove(int x, int y) { 131 | sendTouch(x, y, ACTION_MOVE); 132 | } 133 | 134 | private void sendTouch(int x, int y, byte action) { 135 | mBuffer.clear(); 136 | mBuffer.putInt(10); 137 | mBuffer.put(TYPE_MOTION); 138 | mBuffer.put(action); 139 | mBuffer.putInt(x); 140 | mBuffer.putInt(y); 141 | mBuffer.flip(); 142 | writeBuffer(); 143 | } 144 | 145 | private void writeBuffer() { 146 | if (mChannel != null) { 147 | try { 148 | mChannel.write(mBuffer); 149 | } catch (IOException e) { 150 | e.printStackTrace(); 151 | } 152 | } 153 | } 154 | 155 | public void touchUp() { 156 | mBuffer.clear(); 157 | mBuffer.putInt(2); 158 | mBuffer.put(TYPE_MOTION); 159 | mBuffer.put(ACTION_UP); 160 | mBuffer.flip(); 161 | writeBuffer(); 162 | } 163 | 164 | public void keycode(int keycode){ 165 | mBuffer.clear(); 166 | mBuffer.putInt(5); 167 | mBuffer.put(TYPE_KEYCODE); 168 | mBuffer.putInt(keycode); 169 | mBuffer.flip(); 170 | writeBuffer(); 171 | } 172 | 173 | public void touchScroll(int x, int y, boolean h, int rotation) { 174 | mBuffer.clear(); 175 | mBuffer.putInt(18); 176 | mBuffer.put(TYPE_MOTION); 177 | mBuffer.put(ACTION_SCROLL); 178 | mBuffer.putInt(x); 179 | mBuffer.putInt(y); 180 | float value = rotation > 0 ? -0.8f : 0.8f; 181 | mBuffer.putFloat(h ? value : 0f); 182 | mBuffer.putFloat(value); 183 | mBuffer.flip(); 184 | writeBuffer(); 185 | 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/view/Mirror.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.view; 2 | 3 | import com.android.ddmlib.IDevice; 4 | import com.hlq.mobile.mini.MinicapReceiver; 5 | import com.hlq.mobile.mini.SingleTouch; 6 | import com.hlq.mobile.utils.Tools; 7 | 8 | import javax.imageio.ImageIO; 9 | import javax.swing.*; 10 | import java.awt.*; 11 | import java.awt.event.*; 12 | import java.io.ByteArrayInputStream; 13 | import java.io.IOException; 14 | 15 | public class Mirror implements MinicapReceiver.Callback, WindowListener { 16 | private static final String TAG = "Mirror"; 17 | private final int mTouchPort; 18 | private MinicapReceiver mTask; 19 | private final ImageJPanel mJLabel; 20 | private final JFrame mJFrame; 21 | public static final int WIDTH = 360; 22 | public static final int HEIGHT = 640; 23 | private final int mPort; 24 | private SingleTouch mSingleTouch; 25 | private float mScale; 26 | 27 | 28 | public Mirror(IDevice device, JFrame jFrame ,int index){ 29 | 30 | mJFrame = createJFrame(device); 31 | mJFrame.setLocationRelativeTo(jFrame); 32 | int location = 40 * index; 33 | mPort = 1700 + index; 34 | mTouchPort = 1300 + index; 35 | mJFrame.setLocation(location,location); 36 | mJLabel = new ImageJPanel(); 37 | mJLabel.setSize(WIDTH,HEIGHT); 38 | mJLabel.addMouseListener(new MouseAdapter() { 39 | @Override 40 | public void mousePressed(MouseEvent e) { 41 | int x = e.getX(); 42 | int y = e.getY(); 43 | int [] xy = convertXY(x, y); 44 | if (xy != null && mSingleTouch != null) { 45 | mSingleTouch.touchDown(xy[0],xy[1]); 46 | } 47 | } 48 | 49 | @Override 50 | public void mouseReleased(MouseEvent e) { 51 | if (mSingleTouch != null) { 52 | mSingleTouch.touchUp(); 53 | } 54 | } 55 | 56 | }); 57 | 58 | mJLabel.addMouseMotionListener(new MouseMotionAdapter() { 59 | @Override 60 | public void mouseDragged(MouseEvent e) { 61 | int x = e.getX(); 62 | int y = e.getY(); 63 | int [] xy = convertXY(x, y); 64 | if (xy != null && mSingleTouch != null) { 65 | mSingleTouch.touchMove(xy[0],xy[1]); 66 | } 67 | 68 | } 69 | }); 70 | 71 | mJLabel.addMouseWheelListener(new MouseWheelListener() { 72 | @Override 73 | public void mouseWheelMoved(MouseWheelEvent e) { 74 | int wheelRotation = e.getWheelRotation(); 75 | if (wheelRotation != 0) { 76 | int [] xy = convertXY(e.getX(), e.getY()); 77 | if (xy != null && mSingleTouch != null) { 78 | 79 | mSingleTouch.touchScroll(xy[0],xy[1], (e.getModifiers() & InputEvent.SHIFT_MASK) != 0,wheelRotation); 80 | } 81 | } 82 | } 83 | }); 84 | 85 | mJFrame.add(mJLabel,BorderLayout.CENTER); 86 | addBottomButton(); 87 | } 88 | 89 | private void addBottomButton() { 90 | JPanel panel = new JPanel(new GridLayout(1, 4)); 91 | mJFrame.add(panel,BorderLayout.SOUTH); 92 | panel.add(getJButton("POWER", 26)); 93 | panel.add(getJButton("MENU",82)); 94 | panel.add(getJButton("HOME",3)); 95 | panel.add(getJButton("BACK",4)); 96 | } 97 | 98 | private JButton getJButton(String title, final int keyCode) { 99 | JButton power = new JButton(title); 100 | power.setFocusPainted(false); 101 | power.addActionListener(new ActionListener() { 102 | long preExe; 103 | @Override 104 | public void actionPerformed(ActionEvent e) { 105 | if (keyCode == 26) { 106 | long now = System.currentTimeMillis(); 107 | if (now - preExe > 500) { 108 | preExe = now; 109 | if (mSingleTouch != null) { 110 | mSingleTouch.keycode(keyCode); 111 | } 112 | } 113 | } else { 114 | if (mSingleTouch != null) { 115 | mSingleTouch.keycode(keyCode); 116 | } 117 | } 118 | 119 | } 120 | }); 121 | return power; 122 | } 123 | 124 | private int[] convertXY(int x, int y) { 125 | if (mScale > 0) { 126 | x = (int) (x * mScale); 127 | y = (int) (y * mScale); 128 | return new int[]{x,y}; 129 | } 130 | return null; 131 | } 132 | 133 | public void show(IDevice device){ 134 | if (!mJFrame.isShowing()) { 135 | if (mTask != null) { 136 | mTask.stop(); 137 | } 138 | mTask = new MinicapReceiver(device, this,mPort); 139 | Tools.sExecutor.submit(mTask); 140 | mJFrame.setVisible(true); 141 | if (mSingleTouch != null) { 142 | mSingleTouch.stop(); 143 | } 144 | mSingleTouch = new SingleTouch(); 145 | mSingleTouch.start(device,mTouchPort); 146 | } 147 | } 148 | 149 | 150 | private JFrame createJFrame(IDevice device) { 151 | JFrame jFrame = new JFrame(device.getName()); 152 | 153 | jFrame.setSize(WIDTH,HEIGHT + 60); 154 | jFrame.addWindowListener(this); 155 | jFrame.setLayout(new BorderLayout()); 156 | jFrame.setResizable(false); 157 | return jFrame; 158 | } 159 | 160 | @Override 161 | public void updateFrame(byte[] data) { 162 | try { 163 | Image image = ImageIO.read(new ByteArrayInputStream(data)); 164 | mJLabel.setImage(image); 165 | } catch (IOException e) { 166 | e.printStackTrace(); 167 | } 168 | } 169 | 170 | @Override 171 | public int onDisplaySize(int width, int height) { 172 | mScale = width * 1.0f / WIDTH; 173 | height = (int) (height /mScale); 174 | mJFrame.setSize(WIDTH , height + 60); 175 | mJLabel.setSize(WIDTH,height); 176 | return height; 177 | } 178 | 179 | public void stop(){ 180 | mJLabel.setImage(null); 181 | mTask.stop(); 182 | if (mSingleTouch != null) { 183 | mSingleTouch.stop(); 184 | } 185 | mJFrame.dispose(); 186 | } 187 | 188 | @Override 189 | public void windowOpened(WindowEvent e) { 190 | 191 | } 192 | 193 | @Override 194 | public void windowClosing(WindowEvent e) { 195 | stop(); 196 | } 197 | 198 | @Override 199 | public void windowClosed(WindowEvent e) { 200 | 201 | } 202 | 203 | @Override 204 | public void windowIconified(WindowEvent e) { 205 | 206 | } 207 | 208 | @Override 209 | public void windowDeiconified(WindowEvent e) { 210 | 211 | } 212 | 213 | @Override 214 | public void windowActivated(WindowEvent e) { 215 | 216 | } 217 | 218 | @Override 219 | public void windowDeactivated(WindowEvent e) { 220 | 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/utils/Tools.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.utils; 2 | 3 | import com.android.ddmlib.*; 4 | 5 | import java.io.*; 6 | import java.net.URL; 7 | import java.net.URLDecoder; 8 | import java.util.Enumeration; 9 | import java.util.Properties; 10 | import java.util.concurrent.ExecutorService; 11 | import java.util.concurrent.Executors; 12 | import java.util.zip.ZipEntry; 13 | import java.util.zip.ZipFile; 14 | 15 | public class Tools { 16 | private static final String TAG = "Tools"; 17 | public static final ExecutorService sExecutor = Executors.newCachedThreadPool(); 18 | private static String LOCAL_PATH = null; 19 | 20 | public static final String LIBS_PATH_NAME = "phoneMirrorLibs"; 21 | public static final String SINGLETOUCH_APK = "singletouch.apk"; 22 | 23 | public static String getLocalPath() { 24 | if (LOCAL_PATH != null) { 25 | return LOCAL_PATH; 26 | } 27 | String userDir = System.getProperty("user.dir"); 28 | if (userDir != null) { 29 | if (new File(userDir,"phone-mirror.jar").exists()) { 30 | LOCAL_PATH = userDir; 31 | return userDir; 32 | } 33 | } 34 | URL url = Tools.class.getProtectionDomain().getCodeSource().getLocation(); 35 | String filePath = null; 36 | try { 37 | filePath = URLDecoder.decode(url.getPath(), "utf-8"); 38 | } catch (Exception e) { 39 | e.printStackTrace(); 40 | } 41 | if (filePath != null) { 42 | filePath = new File(filePath).getAbsolutePath(); 43 | if (filePath.endsWith(".jar")) { 44 | filePath = filePath.substring(0, filePath.lastIndexOf(File.separator)); 45 | } else { 46 | int endIndex = filePath.indexOf("PhoneMirror"); 47 | if (endIndex >= 0) { 48 | filePath = filePath.substring(0, endIndex + 11); 49 | } 50 | } 51 | } 52 | LOCAL_PATH = filePath; 53 | return filePath; 54 | } 55 | 56 | public static boolean isEmpty(String src) { 57 | return src == null || src.trim().length() == 0; 58 | } 59 | 60 | public static String executeShellCommand(IDevice device, String cmd) { 61 | CollectingOutputReceiver receiver = new CollectingOutputReceiver(); 62 | try { 63 | device.executeShellCommand(cmd, receiver,10000); 64 | } catch (Exception e) { 65 | e.printStackTrace(); 66 | } 67 | Log.d(TAG, "executeShellCommand : " + cmd ); 68 | return receiver.getOutput(); 69 | } 70 | 71 | public static String getAdbPath(){ 72 | String userDir = getLocalPath(); 73 | if (userDir != null) { 74 | Properties config = getConfigProperties(userDir); 75 | if (config != null) { 76 | String adbPath = config.getProperty("adb.path"); 77 | if (adbPath != null && !adbPath.isEmpty()) { 78 | return adbPath; 79 | } 80 | } 81 | } 82 | Log.d(TAG,"*****************************\nadb路径默认使用jar包同路径下的 :android_tools/bradb.exe\n" + 83 | "自定义配置方法:同路径下创建config.properties文件,配置属性:adb.path=adb绝对路径\n*********************************"); 84 | String os = System.getProperty("os.name"); 85 | Log.d(TAG,"os = " + os); 86 | //TODO:linux 87 | String adb = "bradb.exe"; 88 | if (os != null && os.startsWith("Mac")) { 89 | adb = "adb"; 90 | } 91 | return userDir + File.separator + "android_tools" + File.separator + adb; 92 | } 93 | 94 | private static Properties getConfigProperties(String userDir) { 95 | File propFile = new File(userDir, "config.properties"); 96 | if (propFile.exists()) { 97 | FileInputStream inStream = null; 98 | try { 99 | inStream = new FileInputStream(propFile); 100 | Properties properties = new Properties(); 101 | properties.load(inStream); 102 | return properties; 103 | } catch (IOException e) { 104 | e.printStackTrace(); 105 | }finally { 106 | if (inStream != null) { 107 | try { 108 | inStream.close(); 109 | } catch (IOException e) { 110 | e.printStackTrace(); 111 | } 112 | } 113 | } 114 | } 115 | return null; 116 | } 117 | 118 | public static String getMinicapLibPath(){ 119 | return getLocalPath() + File.separator + LIBS_PATH_NAME + File.separator + "minicapLib" + File.separator; 120 | } 121 | 122 | public static void unzipMinicapLib(){ 123 | unzipResLib("minicapLib.zip"); 124 | } 125 | 126 | public static void unzipResLib(String resName){ 127 | String userDir = getLocalPath(); 128 | Log.d(TAG,"unzipResLib : " + userDir + ",resName = " + resName); 129 | InputStream inputStream = Tools.class.getClassLoader().getResourceAsStream(resName); 130 | if (inputStream != null) { 131 | File libFile = new File(userDir, resName); 132 | copyFile(inputStream,libFile); 133 | String phoneMirrorLibsPath = userDir + File.separator + LIBS_PATH_NAME; 134 | ZipFile zipFile = null; 135 | try { 136 | zipFile = new ZipFile(libFile); 137 | Enumeration entries = zipFile.entries(); 138 | File file; 139 | while (entries.hasMoreElements()){ 140 | ZipEntry entry = entries.nextElement(); 141 | file = new File(phoneMirrorLibsPath, entry.getName()); 142 | if (entry.isDirectory()) { 143 | if (!file.isDirectory()) { 144 | file.mkdirs(); 145 | } 146 | } else { 147 | copyFile(zipFile.getInputStream(entry),file); 148 | } 149 | } 150 | } catch (IOException e) { 151 | e.printStackTrace(); 152 | }finally { 153 | if (zipFile != null) { 154 | try { 155 | zipFile.close(); 156 | } catch (IOException e) { 157 | e.printStackTrace(); 158 | } 159 | } 160 | if (libFile.exists()) { 161 | libFile.delete(); 162 | } 163 | } 164 | } 165 | } 166 | 167 | public static void copySingleTouchApk(File apkFile){ 168 | InputStream inputStream = Tools.class.getClassLoader().getResourceAsStream(SINGLETOUCH_APK); 169 | if (inputStream != null) { 170 | File dir = apkFile.getParentFile(); 171 | if (dir != null && !dir.exists()) { 172 | dir.mkdir(); 173 | } 174 | copyFile(inputStream,apkFile); 175 | } 176 | } 177 | 178 | public static void copyFile(InputStream inputStream,File outFile){ 179 | FileOutputStream out = null; 180 | try { 181 | out = new FileOutputStream(outFile); 182 | int len; 183 | byte[] bytes = new byte[8092]; 184 | while ((len = inputStream.read(bytes)) != -1){ 185 | out.write(bytes,0,len); 186 | } 187 | } catch (Exception e) { 188 | e.printStackTrace(); 189 | }finally { 190 | try { 191 | inputStream.close(); 192 | } catch (IOException e) { 193 | e.printStackTrace(); 194 | } 195 | if (out != null) { 196 | try { 197 | out.close(); 198 | } catch (IOException e) { 199 | e.printStackTrace(); 200 | } 201 | } 202 | } 203 | } 204 | 205 | 206 | } 207 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/view/VFlowLayout.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.view; 2 | 3 | import java.awt.*; 4 | 5 | public class VFlowLayout extends FlowLayout { 6 | /** 7 | * 8 | */ 9 | private static final long serialVersionUID = 1L; 10 | 11 | /** 12 | * Specify alignment top. 13 | */ 14 | public static final int TOP = 0; 15 | 16 | /** 17 | * Specify a middle alignment. 18 | */ 19 | public static final int MIDDLE = 1; 20 | 21 | /** 22 | * Specify the alignment to be bottom. 23 | */ 24 | public static final int BOTTOM = 2; 25 | 26 | int hgap; 27 | int vgap; 28 | boolean hfill; 29 | boolean vfill; 30 | 31 | 32 | /** 33 | * Construct a new VerticalFlowLayout with a middle alignment, and the fill 34 | * to edge flag set. 35 | */ 36 | public VFlowLayout() { 37 | this(TOP, 5, 5, true, false); 38 | } 39 | 40 | /** 41 | * Construct a new VerticalFlowLayout with a middle alignment. 42 | * 43 | * @param hfill the fill to edge flag 44 | * @param vfill the vertical fill in pixels. 45 | */ 46 | public VFlowLayout(boolean hfill, boolean vfill) { 47 | this(TOP, 5, 5, hfill, vfill); 48 | } 49 | 50 | /** 51 | * Construct a new VerticalFlowLayout with a middle alignment. 52 | * 53 | * @param align the alignment value 54 | */ 55 | public VFlowLayout(int align) { 56 | this(align, 5, 5, true, false); 57 | } 58 | 59 | /** 60 | * Construct a new VerticalFlowLayout. 61 | * 62 | * @param align the alignment value 63 | * @param hfill the horizontalfill in pixels. 64 | * @param vfill the vertical fill in pixels. 65 | */ 66 | public VFlowLayout(int align, boolean hfill, boolean vfill) { 67 | this(align, 5, 5, hfill, vfill); 68 | } 69 | 70 | /** 71 | * Construct a new VerticalFlowLayout. 72 | * 73 | * @param align the alignment value 74 | * @param hgap the horizontal gap variable 75 | * @param vgap the vertical gap variable 76 | * @param hfill the fill to edge flag 77 | * @param vfill true if the panel should vertically fill. 78 | */ 79 | public VFlowLayout(int align, int hgap, int vgap, boolean hfill, boolean vfill) { 80 | setAlignment(align); 81 | this.hgap = hgap; 82 | this.vgap = vgap; 83 | this.hfill = hfill; 84 | this.vfill = vfill; 85 | } 86 | 87 | /** 88 | * Returns the preferred dimensions given the components in the target 89 | * container. 90 | * 91 | * @param target the component to lay out 92 | */ 93 | public Dimension preferredLayoutSize(Container target) { 94 | Dimension tarsiz = new Dimension(0, 0); 95 | 96 | for (int i = 0; i < target.getComponentCount(); i++) { 97 | Component m = target.getComponent(i); 98 | 99 | if (m.isVisible()) { 100 | Dimension d = m.getPreferredSize(); 101 | tarsiz.width = Math.max(tarsiz.width, d.width); 102 | 103 | if (i > 0) { 104 | tarsiz.height += hgap; 105 | } 106 | 107 | tarsiz.height += d.height; 108 | } 109 | } 110 | 111 | Insets insets = target.getInsets(); 112 | tarsiz.width += insets.left + insets.right + hgap * 2; 113 | tarsiz.height += insets.top + insets.bottom + vgap * 2; 114 | 115 | return tarsiz; 116 | } 117 | 118 | /** 119 | * Returns the minimum size needed to layout the target container. 120 | * 121 | * @param target the component to lay out. 122 | * @return the minimum layout dimension. 123 | */ 124 | public Dimension minimumLayoutSize(Container target) { 125 | Dimension tarsiz = new Dimension(0, 0); 126 | 127 | for (int i = 0; i < target.getComponentCount(); i++) { 128 | Component m = target.getComponent(i); 129 | 130 | if (m.isVisible()) { 131 | Dimension d = m.getMinimumSize(); 132 | tarsiz.width = Math.max(tarsiz.width, d.width); 133 | 134 | if (i > 0) { 135 | tarsiz.height += vgap; 136 | } 137 | 138 | tarsiz.height += d.height; 139 | } 140 | } 141 | 142 | Insets insets = target.getInsets(); 143 | tarsiz.width += insets.left + insets.right + hgap * 2; 144 | tarsiz.height += insets.top + insets.bottom + vgap * 2; 145 | 146 | return tarsiz; 147 | } 148 | 149 | /** 150 | * Set true to fill vertically. 151 | * 152 | * @param vfill true to fill vertically. 153 | */ 154 | public void setVerticalFill(boolean vfill) { 155 | this.vfill = vfill; 156 | } 157 | 158 | /** 159 | * Returns true if the layout vertically fills. 160 | * 161 | * @return true if vertically fills the layout using the specified. 162 | */ 163 | public boolean getVerticalFill() { 164 | return vfill; 165 | } 166 | 167 | /** 168 | * Set to true to enable horizontally fill. 169 | * 170 | * @param hfill true to fill horizontally. 171 | */ 172 | public void setHorizontalFill(boolean hfill) { 173 | this.hfill = hfill; 174 | } 175 | 176 | /** 177 | * Returns true if the layout horizontally fills. 178 | * 179 | * @return true if horizontally fills. 180 | */ 181 | public boolean getHorizontalFill() { 182 | return hfill; 183 | } 184 | 185 | /** 186 | * places the components defined by first to last within the target 187 | * container using the bounds box defined. 188 | * 189 | * @param target the container. 190 | * @param x the x coordinate of the area. 191 | * @param y the y coordinate of the area. 192 | * @param width the width of the area. 193 | * @param height the height of the area. 194 | * @param first the first component of the container to place. 195 | * @param last the last component of the container to place. 196 | */ 197 | private void placethem(Container target, int x, int y, int width, int height, int first, int last) { 198 | int align = getAlignment(); 199 | 200 | if (align == MIDDLE) { 201 | y += height / 2; 202 | } 203 | 204 | if (align == BOTTOM) { 205 | y += height; 206 | } 207 | 208 | for (int i = first; i < last; i++) { 209 | Component m = target.getComponent(i); 210 | Dimension md = m.getSize(); 211 | 212 | if (m.isVisible()) { 213 | int px = x + (width - md.width) / 2; 214 | m.setLocation(px, y); 215 | y += vgap + md.height; 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * Lays out the container. 222 | * 223 | * @param target the container to lay out. 224 | */ 225 | public void layoutContainer(Container target) { 226 | Insets insets = target.getInsets(); 227 | int maxheight = target.getSize().height - (insets.top + insets.bottom + vgap * 2); 228 | int maxwidth = target.getSize().width - (insets.left + insets.right + hgap * 2); 229 | int numcomp = target.getComponentCount(); 230 | int x = insets.left + hgap, y = 0; 231 | int colw = 0, start = 0; 232 | 233 | for (int i = 0; i < numcomp; i++) { 234 | Component m = target.getComponent(i); 235 | 236 | if (m.isVisible()) { 237 | Dimension d = m.getPreferredSize(); 238 | 239 | // fit last component to remaining height 240 | if ((this.vfill) && (i == (numcomp - 1))) { 241 | d.height = Math.max((maxheight - y), m.getPreferredSize().height); 242 | } 243 | 244 | // fit component size to container width 245 | if (this.hfill) { 246 | m.setSize(maxwidth, d.height); 247 | d.width = maxwidth; 248 | } else { 249 | m.setSize(d.width, d.height); 250 | } 251 | 252 | if (y + d.height > maxheight) { 253 | placethem(target, x, insets.top + vgap, colw, maxheight - y, start, i); 254 | y = d.height; 255 | x += hgap + colw; 256 | colw = d.width; 257 | start = i; 258 | } else { 259 | if (y > 0) { 260 | y += vgap; 261 | } 262 | 263 | y += d.height; 264 | colw = Math.max(colw, d.width); 265 | } 266 | } 267 | } 268 | 269 | placethem(target, x, insets.top + vgap, colw, maxheight - y, start, numcomp); 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /src/main/java/com/hlq/mobile/mini/MinicapReceiver.java: -------------------------------------------------------------------------------- 1 | package com.hlq.mobile.mini; 2 | 3 | import com.android.ddmlib.IDevice; 4 | import com.hlq.mobile.adb.AndroidBridge; 5 | import com.hlq.mobile.utils.Log; 6 | import com.hlq.mobile.utils.Tools; 7 | import com.hlq.mobile.view.Mirror; 8 | 9 | import java.io.*; 10 | import java.net.InetSocketAddress; 11 | import java.nio.ByteBuffer; 12 | import java.nio.channels.SocketChannel; 13 | import java.util.concurrent.CountDownLatch; 14 | import java.util.concurrent.TimeUnit; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | public class MinicapReceiver implements Runnable{ 19 | private static final String TAG = "MinicapReceiver"; 20 | 21 | private boolean stoped = false; 22 | private IDevice mDevice; 23 | private Callback mCallback; 24 | private int mPort; 25 | private CountDownLatch mLatch; 26 | private SocketChannel mChannel; 27 | private int mPid; 28 | private int mHeight = Mirror.HEIGHT; 29 | 30 | public MinicapReceiver(IDevice iDevice ,Callback callback ,int port){ 31 | mDevice = iDevice; 32 | mCallback = callback; 33 | mPort = port; 34 | } 35 | 36 | @Override 37 | public void run() { 38 | Process process = prepareMinicap(); 39 | if (process != null) { 40 | minicapRecord(mCallback); 41 | process.destroy(); 42 | destroy(); 43 | } 44 | } 45 | 46 | public void destroy() { 47 | if (mDevice != null && mDevice.isOnline()) { 48 | if (mPid != 0) { 49 | String result = Tools.executeShellCommand(mDevice, "kill -9 " + mPid); 50 | Log.d(TAG,"kill result = " + result); 51 | } 52 | Tools.executeShellCommand(mDevice, "rm /data/local/tmp/minicap"); 53 | 54 | } 55 | if (mLatch != null) { 56 | mLatch.countDown(); 57 | } 58 | } 59 | 60 | public void stop(){ 61 | if (!stoped) { 62 | stoped = true; 63 | if (mChannel != null) { 64 | try { 65 | mChannel.close(); 66 | } catch (IOException e) { 67 | e.printStackTrace(); 68 | } 69 | mChannel = null; 70 | } 71 | mLatch = new CountDownLatch(1); 72 | boolean await = false; 73 | try { 74 | await = mLatch.await(2000, TimeUnit.MILLISECONDS); 75 | } catch (InterruptedException e) { 76 | e.printStackTrace(); 77 | } 78 | mLatch = null; 79 | 80 | Log.d(TAG,"stop minicap await = " + await); 81 | } 82 | } 83 | 84 | public Process prepareMinicap() { 85 | String sdk = mDevice.getProperty("ro.build.version.sdk"); 86 | String abi = mDevice.getProperty("ro.product.cpu.abi"); 87 | if (Tools.isEmpty(sdk)) { 88 | sdk = Tools.executeShellCommand(mDevice,"getprop ro.build.version.sdk").trim(); 89 | } 90 | if (Tools.isEmpty(abi)){ 91 | abi = Tools.executeShellCommand(mDevice,"getprop ro.product.cpu.abi").trim(); 92 | } 93 | Log.d(TAG,"mDevice.getProperty : sdk = " + sdk + " abi = " + abi); 94 | String minicapLibPath = Tools.getMinicapLibPath(); 95 | String minicapLib = minicapLibPath + "aosp" + File.separator + "libs" + File.separator + "android-" + sdk + File.separator + abi + File.separator + "minicap.so"; 96 | String minicap = minicapLibPath + "libs" + File.separator + abi + File.separator + "minicap"; 97 | if (!new File(minicap).exists()) { 98 | Tools.unzipMinicapLib(); 99 | } 100 | if (!new File(minicapLib).exists()) { 101 | Tools.unzipMinicapLib(); 102 | } 103 | try { 104 | mDevice.pushFile(minicapLib,"/data/local/tmp/minicap.so"); 105 | mDevice.pushFile(minicap,"/data/local/tmp/minicap"); 106 | Tools.executeShellCommand(mDevice,"chmod 777 /data/local/tmp/minicap"); 107 | } catch (Exception e) { 108 | e.printStackTrace(); 109 | } 110 | 111 | try { 112 | mDevice.createForward(mPort,"minicap", IDevice.DeviceUnixSocketNamespace.ABSTRACT); 113 | Process process = Runtime.getRuntime().exec(AndroidBridge.sAdbPath + " -s " + mDevice.getSerialNumber() + " shell LD_LIBRARY_PATH=/data/local/tmp /data/local/tmp/minicap -P "+getDisplaySize(mDevice)+"@"+ Mirror.WIDTH +"x"+ mHeight +"/0"); 114 | BufferedReader reader = new BufferedReader(new InputStreamReader(new SequenceInputStream(process.getInputStream(), process.getErrorStream()))); 115 | try { 116 | String line; 117 | while ((line = reader.readLine()) != null){ 118 | Log.d(TAG+"_reader","line = " + line); 119 | if (line.contains("JPG encoder")) { 120 | break; 121 | } 122 | } 123 | }catch (IOException e){ 124 | e.printStackTrace(); 125 | }finally { 126 | try { 127 | reader.close(); 128 | } catch (IOException e) { 129 | e.printStackTrace(); 130 | } 131 | } 132 | return process; 133 | } catch (Exception e) { 134 | e.printStackTrace(); 135 | } 136 | return null; 137 | } 138 | private String getDisplaySize(IDevice iDevice) { 139 | String result = Tools.executeShellCommand(iDevice,"wm size"); 140 | Log.d(TAG,"getDisplaySize : " + result); 141 | if (result != null) { 142 | try { 143 | Pattern pattern = Pattern.compile("(\\d+)x(\\d+)"); 144 | Matcher matcher = pattern.matcher(result); 145 | if (matcher.find()) { 146 | String width = matcher.group(1); 147 | String height = matcher.group(2); 148 | Log.d(TAG, "getDisplaySize : width = " + width + ", height = " + height); 149 | if (mCallback != null) { 150 | mHeight = mCallback.onDisplaySize(Integer.parseInt(width), Integer.parseInt(height)); 151 | } 152 | return width + "x" + height; 153 | } 154 | }catch (Exception e){ 155 | e.printStackTrace(); 156 | } 157 | } 158 | return "1080x1920"; 159 | } 160 | 161 | 162 | public void minicapRecord(Callback callback) { 163 | try { 164 | mChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",mPort)); 165 | mChannel.socket().setTcpNoDelay(true); 166 | mChannel.socket().setKeepAlive(true); 167 | if (mChannel.finishConnect()) { 168 | Log.d(TAG,"finish connect port = " + mPort); 169 | ByteBuffer header = ByteBuffer.allocate(24); 170 | fillBuffer(header, mChannel); 171 | header.flip(); 172 | log("version = " + header.get()); 173 | int headLen = header.get(); 174 | log("headLen = " + headLen); 175 | if (headLen == 24) { 176 | mPid = getInt(header); 177 | log("pid = " + mPid); 178 | log("realWidth = " + getInt(header)); 179 | log("realHeight = " + getInt(header)); 180 | log("virWidth = " + getInt(header)); 181 | log("virHeight = " +getInt(header)); 182 | log("orientation = " + header.get()); 183 | log("Quirk = " + header.get()); 184 | } 185 | ByteBuffer frameLen = ByteBuffer.allocate(4); 186 | ByteBuffer frame; 187 | int len; 188 | while (!stoped) { 189 | fillBuffer(frameLen, mChannel); 190 | if (!frameLen.hasRemaining()) { 191 | frameLen.flip(); 192 | len = getInt(frameLen); 193 | if (len > 1) { 194 | frame = ByteBuffer.allocate(len); 195 | fillBuffer(frame, mChannel); 196 | frame.flip(); 197 | if (callback != null) { 198 | callback.updateFrame(frame.array()); 199 | } 200 | } 201 | } else { 202 | log("len has remain = " + frameLen.remaining()); 203 | } 204 | frameLen.clear(); 205 | } 206 | } 207 | log("record end..."); 208 | } catch (Exception e) { 209 | e.printStackTrace(); 210 | } finally { 211 | if (mChannel != null) { 212 | try { 213 | mChannel.close(); 214 | } catch (IOException e) { 215 | e.printStackTrace(); 216 | } 217 | } 218 | } 219 | } 220 | 221 | private int getInt(ByteBuffer buffer) { 222 | int result = 0; 223 | for (int i = 0; i < 4; i++) { 224 | result |= (buffer.get() & 0xff )<< (i * 8); 225 | } 226 | return result; 227 | } 228 | private void fillBuffer(ByteBuffer buffer,SocketChannel channel) throws IOException{ 229 | int len; 230 | do { 231 | len = channel.read(buffer); 232 | }while (buffer.hasRemaining() && len != -1); 233 | } 234 | private void log(String msg){ 235 | System.out.println("ReceiveJpg : " + msg); 236 | } 237 | 238 | 239 | public interface Callback { 240 | void updateFrame(byte[] data); 241 | 242 | int onDisplaySize(int width,int height); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------