├── .classpath
├── .cproject
├── .gitignore
├── .project
├── .settings
├── org.eclipse.cdt.codan.core.prefs
└── org.eclipse.jdt.core.prefs
├── AndroidManifest.xml
├── assets
└── .gitignore
├── jni
├── Android.mk
├── events.h
├── input_recorder.c
├── input_replayer.c
├── jni_log.h
├── recorder.h
├── replayer.h
└── uinput.h
├── libs
├── android-support-v4.jar
└── armeabi
│ ├── libinputrecorder.so
│ └── libinputreplayer.so
├── lint.xml
├── obj
└── local
│ └── armeabi
│ ├── libinputrecorder.so
│ └── libinputreplayer.so
├── proguard-project.txt
├── project.properties
├── res
├── drawable-hdpi
│ └── ic_launcher.png
├── drawable-ldpi
│ └── .gitignore
├── drawable-mdpi
│ └── ic_launcher.png
├── drawable-xhdpi
│ └── ic_launcher.png
├── layout
│ ├── activity_main.xml
│ └── repeat_layout.xml
└── values
│ ├── dimens.xml
│ └── strings.xml
└── src
└── com
└── dll
├── touchrepeater
├── InputRecorder.java
├── InputReplayer.java
├── MainActivity.java
├── RepeatLayout.java
├── RepeatService.java
└── RootPermission.java
└── util
└── FilePathUtil.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.cproject:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 |
15 | # Gradle files
16 | .gradle/
17 | build/
18 |
19 | # Local configuration file (sdk path, etc)
20 | local.properties
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Log Files
26 | *.log
27 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | TouchRepeater
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.cdt.managedbuilder.core.genmakebuilder
10 | clean,full,incremental,
11 |
12 |
13 | ?children?
14 | ?children?=?name?=entry\\\\\\\|\\\|?name?=entry\\\\\\\|\\\|\|?name?=outputEntries\||
15 |
16 |
17 | ?name?
18 |
19 |
20 |
21 | org.eclipse.cdt.make.core.append_environment
22 | true
23 |
24 |
25 | org.eclipse.cdt.make.core.buildArguments
26 |
27 |
28 |
29 | org.eclipse.cdt.make.core.buildCommand
30 | ndk-build
31 |
32 |
33 | org.eclipse.cdt.make.core.cleanBuildTarget
34 | clean
35 |
36 |
37 | org.eclipse.cdt.make.core.contents
38 | org.eclipse.cdt.make.core.activeConfigSettings
39 |
40 |
41 | org.eclipse.cdt.make.core.enableAutoBuild
42 | false
43 |
44 |
45 | org.eclipse.cdt.make.core.enableCleanBuild
46 | true
47 |
48 |
49 | org.eclipse.cdt.make.core.enableFullBuild
50 | true
51 |
52 |
53 | org.eclipse.cdt.make.core.stopOnError
54 | true
55 |
56 |
57 | org.eclipse.cdt.make.core.useDefaultBuildCmd
58 | false
59 |
60 |
61 |
62 |
63 | com.android.ide.eclipse.adt.ResourceManagerBuilder
64 |
65 |
66 |
67 |
68 | com.android.ide.eclipse.adt.PreCompilerBuilder
69 |
70 |
71 |
72 |
73 | org.eclipse.jdt.core.javabuilder
74 |
75 |
76 |
77 |
78 | com.android.ide.eclipse.adt.ApkBuilder
79 |
80 |
81 |
82 |
83 | org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder
84 | full,incremental,
85 |
86 |
87 |
88 |
89 |
90 | com.android.ide.eclipse.adt.AndroidNature
91 | org.eclipse.jdt.core.javanature
92 | org.eclipse.cdt.core.cnature
93 | org.eclipse.cdt.core.ccnature
94 | org.eclipse.cdt.managedbuilder.core.managedBuildNature
95 | org.eclipse.cdt.managedbuilder.core.ScannerConfigNature
96 |
97 |
98 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.cdt.codan.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.cdt.codan.checkers.errnoreturn=Warning
3 | org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},implicit\=>false}
4 | org.eclipse.cdt.codan.checkers.errreturnvalue=Error
5 | org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
6 | org.eclipse.cdt.codan.checkers.noreturn=Error
7 | org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},implicit\=>false}
8 | org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error
9 | org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
10 | org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error
11 | org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
12 | org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning
13 | org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
14 | org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error
15 | org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
16 | org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning
17 | org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false}
18 | org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning
19 | org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},unknown\=>false,exceptions\=>()}
20 | org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error
21 | org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
22 | org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning
23 | org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},skip\=>true}
24 | org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error
25 | org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
26 | org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error
27 | org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
28 | org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error
29 | org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
30 | org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error
31 | org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
32 | org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error
33 | org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
34 | org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error
35 | org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
36 | org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error
37 | org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
38 | org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info
39 | org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},pattern\=>"^[a-z]",macro\=>true,exceptions\=>()}
40 | org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning
41 | org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
42 | org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error
43 | org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
44 | org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error
45 | org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
46 | org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error
47 | org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
48 | org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning
49 | org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
50 | org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning
51 | org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
52 | org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning
53 | org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true,exceptions\=>()}
54 | org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning
55 | org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},paramNot\=>false}
56 | org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning
57 | org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},else\=>false,afterelse\=>false}
58 | org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error
59 | org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
60 | org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning
61 | org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true}
62 | org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning
63 | org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true}
64 | org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning
65 | org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},macro\=>true,exceptions\=>("@(\#)","$Id")}
66 | org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error
67 | org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true}}
68 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
3 | org.eclipse.jdt.core.compiler.compliance=1.6
4 | org.eclipse.jdt.core.compiler.source=1.6
5 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
12 |
13 |
14 |
19 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/assets/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/assets/.gitignore
--------------------------------------------------------------------------------
/jni/Android.mk:
--------------------------------------------------------------------------------
1 | LOCAL_PATH:= $(call my-dir)
2 |
3 |
4 | include $(CLEAR_VARS)
5 | LOCAL_LDLIBS := -llog
6 | LOCAL_MODULE := inputrecorder
7 | LOCAL_SRC_FILES := input_recorder.c
8 | LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)
9 | include $(BUILD_SHARED_LIBRARY)
10 |
11 | include $(CLEAR_VARS)
12 | LOCAL_LDLIBS := -llog
13 | LOCAL_MODULE := inputreplayer
14 | LOCAL_SRC_FILES := input_replayer.c
15 | LOCAL_MODULE_PATH := $(TARGET_OUT_EXECUTABLES)
16 | include $(BUILD_SHARED_LIBRARY)
--------------------------------------------------------------------------------
/jni/events.h:
--------------------------------------------------------------------------------
1 | /*
2 | * events.h
3 | *
4 | * Created on: 2014年6月13日
5 | * Author: DLL
6 | */
7 |
8 | #ifndef EVENTS_H_
9 | #define EVENTS_H_
10 |
11 | const char *event_prefix = "/dev/input/";
12 | const char *event_devices[] = { "event0", "event1", "event2", "event3", "event4", "event5", "event6", "event7" };
13 | #define NUM_DEVICES (sizeof(event_devices) / sizeof(char *))
14 | #endif /* EVENTS_H_ */
15 |
--------------------------------------------------------------------------------
/jni/input_recorder.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "jni_log.h"
4 | #include "recorder.h"
5 |
6 | JNIEXPORT void JNICALL Java_com_dll_touchrepeater_InputRecorder_nativeStop(JNIEnv *env,
7 | jobject thiz, jlong recorderPointer)
8 | {
9 | struct recorder* recorder = (void *) (long) recorderPointer;
10 | stop(recorder);
11 | }
12 |
13 | JNIEXPORT jlong JNICALL Java_com_dll_touchrepeater_InputRecorder_nativeInit(JNIEnv *env,
14 | jobject thiz, jstring recordFilePath)
15 | {
16 | const char* filePath = (*env)->GetStringUTFChars(env, recordFilePath, 0);
17 | struct recorder* recorder = init(filePath);
18 |
19 | (*env)->ReleaseStringUTFChars(env, recordFilePath, filePath);
20 | return (jlong) (long) recorder;
21 | }
22 |
23 | JNIEXPORT jint JNICALL Java_com_dll_touchrepeater_InputRecorder_nativeRecord(JNIEnv *env,
24 | jobject thiz, jlong recorderPointer)
25 | {
26 | struct recorder* recorder = (void *) (long) recorderPointer;
27 | int recordResult = record(recorder);
28 | destroy(recorder);
29 | return recordResult;
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/jni/input_replayer.c:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include "jni_log.h"
4 | #include "replayer.h"
5 |
6 | #ifndef _Included_com_dll_touchrepeater_InputReplayer
7 | #define _Included_com_dll_touchrepeater_InputReplayer
8 |
9 | JNIEXPORT jlong JNICALL Java_com_dll_touchrepeater_InputReplayer_nativeInit
10 | (JNIEnv *env, jobject thiz, jstring replayFilePath)
11 | {
12 | const char *filePath = (*env)->GetStringUTFChars(env, replayFilePath, 0);
13 | struct replayer* replayer = init(filePath);
14 | (*env)->ReleaseStringUTFChars(env, replayFilePath, filePath);
15 | return (jlong)(long) replayer;
16 | }
17 |
18 | JNIEXPORT jint JNICALL Java_com_dll_touchrepeater_InputReplayer_nativeReplay
19 | (JNIEnv *env, jobject thiz, jlong replayerPointer, jint repeatTimes)
20 | {
21 | struct replayer* replayer = (void *)(long) replayerPointer;
22 | int replayResult = replay(replayer, repeatTimes);
23 | destroy(replayer);
24 | return replayResult;
25 | }
26 |
27 | JNIEXPORT void JNICALL Java_com_dll_touchrepeater_InputReplayer_nativeStop
28 | (JNIEnv *env, jobject thiz, jlong replayerPointer)
29 | {
30 | struct replayer* replayer = (void *)(long) replayerPointer;
31 | stop(replayer);
32 | }
33 |
34 | #endif
35 |
--------------------------------------------------------------------------------
/jni/jni_log.h:
--------------------------------------------------------------------------------
1 | /*
2 | * jni_log.h
3 | *
4 | * Created on: 2014年6月13日
5 | * Author: DLL
6 | */
7 |
8 | #include
9 |
10 | #ifndef JNI_LOG_H_
11 | #define JNI_LOG_H_
12 |
13 |
14 |
15 | #define TAG "JNI_LOG"
16 | #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG , TAG, __VA_ARGS__)
17 | #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
18 | #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
19 | #define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
20 | #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
21 |
22 | #endif /* JNI_LOG_H_ */
23 |
--------------------------------------------------------------------------------
/jni/recorder.h:
--------------------------------------------------------------------------------
1 | /*
2 | * recorder.h
3 | *
4 | * Created on: 2014年6月13日
5 | * Author: DLL
6 | */
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 |
15 | #include "jni_log.h"
16 | #include "events.h"
17 |
18 | #ifndef RECORDER_H_
19 | #define RECORDER_H_
20 |
21 | struct recorder
22 | {
23 | int is_recording;
24 | int out_fd;
25 | struct pollfd *in_fds;
26 | };
27 |
28 | void destroy(struct recorder* recorder);
29 |
30 | struct recorder* init(const char* filePath)
31 | {
32 | char buffer[256];
33 | int fd, i;
34 | struct recorder *recorder = malloc(sizeof(struct recorder));
35 | memset(recorder, 0, sizeof(recorder));
36 | struct pollfd *in_fds = malloc(NUM_DEVICES * sizeof(struct pollfd));
37 | recorder->in_fds = in_fds;
38 |
39 | recorder->out_fd = open(filePath, O_WRONLY | O_CREAT | O_TRUNC);
40 | if (recorder->out_fd < 0)
41 | {
42 | LOGD("Couldn't open output file: %s", filePath);
43 | destroy(recorder);
44 | return 0;
45 | }
46 | for (i = 0; i < NUM_DEVICES; i++)
47 | {
48 | sprintf(buffer, "%s%s", event_prefix, event_devices[i]);
49 | recorder->in_fds[i].events = POLLIN;
50 | recorder->in_fds[i].fd = open(buffer, O_RDONLY | O_NDELAY);
51 | if (recorder->in_fds[i].fd < 0)
52 | {
53 | LOGD("Couldn't open input device %s", buffer);
54 | destroy(recorder);
55 | return 0;
56 | }
57 | }
58 | recorder->is_recording = 1;
59 | LOGD("recorder init ok");
60 | return recorder;
61 | }
62 |
63 | void stop(struct recorder * recorder)
64 | {
65 | recorder->is_recording = 0;
66 | }
67 |
68 | int record(struct recorder * recorder)
69 | {
70 | int i, num_read;
71 | struct input_event event;
72 | while (recorder->is_recording)
73 | {
74 | if (poll(recorder->in_fds, NUM_DEVICES, -1) < 0)
75 | {
76 | LOGD("poll error");
77 | return 1;
78 | }
79 | if (recorder->is_recording)
80 | {
81 | for (i = 0; i < NUM_DEVICES; i++)
82 | {
83 | if (recorder->in_fds[i].revents & POLLIN)
84 | {
85 | num_read = read(recorder->in_fds[i].fd, &event, sizeof(event));
86 | if (num_read != sizeof(event))
87 | {
88 | LOGD("read error");
89 | return 2;
90 | }
91 | if ((write(recorder->out_fd, &i, sizeof(i)) != sizeof(i)
92 | || write(recorder->out_fd, &event, sizeof(event)) != sizeof(event)))
93 | {
94 | LOGD("Write error\n");
95 | return 3;
96 | }
97 | LOGD("event: %d %08x %08x %08x", i, event.type, event.code, event.value);
98 | }
99 | }
100 | }
101 | else
102 | {
103 | break;
104 | }
105 | }
106 | return 0;
107 | }
108 |
109 | void closeFiles(struct recorder* recorder)
110 | {
111 | int result = 0, i;
112 | for (i = 0; i < NUM_DEVICES; i++)
113 | {
114 | if (recorder->in_fds[i].fd > 0)
115 | {
116 | result += close(recorder->in_fds[i].fd);
117 | }
118 | }
119 | if (recorder->out_fd > 0)
120 | {
121 | result += close(recorder->out_fd);
122 | }
123 | LOGD("close file: %d", result);
124 | }
125 |
126 | void destroy(struct recorder* recorder)
127 | {
128 | closeFiles(recorder);
129 | free(recorder->in_fds);
130 | free(recorder);
131 | }
132 |
133 | #endif /* RECORDER_H_ */
134 |
--------------------------------------------------------------------------------
/jni/replayer.h:
--------------------------------------------------------------------------------
1 | /*
2 | * replayer.h
3 | *
4 | * Created on: 2014年6月13日
5 | * Author: DLL
6 | */
7 |
8 | #include
9 | #include
10 | #include
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 |
17 | #include "events.h"
18 | #include "uinput.h"
19 |
20 | #ifndef REPLAYER_H_
21 | #define REPLAYER_H_
22 |
23 | struct replayer
24 | {
25 | int is_replaying;
26 | int in_fd;
27 | int *out_fds;
28 | int num_events;
29 | char file_path[256];
30 | };
31 |
32 | void destroy(struct replayer* replayer);
33 |
34 | struct replayer* init(const char* filePath)
35 | {
36 | struct stat statinfo;
37 |
38 | if (stat(filePath, &statinfo) == -1)
39 | {
40 | LOGD("Couldn't stat input\n");
41 | return 0;
42 | }
43 |
44 | struct replayer *replayer = malloc(sizeof(struct replayer));
45 | memset(replayer, 0, sizeof(struct replayer));
46 | int *out_fds = malloc(NUM_DEVICES * sizeof(int));
47 | replayer->out_fds = out_fds;
48 | strcpy(replayer->file_path, filePath);
49 | char buffer[256];
50 | int i;
51 |
52 | for (i = 0; i < NUM_DEVICES; i++)
53 | {
54 | sprintf(buffer, "%s%s", event_prefix, event_devices[i]);
55 | replayer->out_fds[i] = open(buffer, O_WRONLY | O_NDELAY);
56 | if (replayer->out_fds[i] < 0)
57 | {
58 | LOGD("Couldn't open output device\n");
59 | destroy(replayer);
60 | return 0;
61 | }
62 | }
63 | replayer->num_events = statinfo.st_size / (sizeof(struct input_event) + sizeof(int));
64 | if ((replayer->in_fd = open(filePath, O_RDONLY)) < 0)
65 | {
66 | LOGD("Couldn't open input\n");
67 | destroy(replayer);
68 | return 0;
69 | }
70 |
71 | ioctl(replayer->out_fds[3], UI_SET_EVBIT, EV_KEY);
72 | ioctl(replayer->out_fds[3], UI_SET_EVBIT, EV_REP);
73 | ioctl(replayer->out_fds[1], UI_SET_EVBIT, EV_ABS);
74 |
75 | replayer->is_replaying = 1;
76 | LOGD("replayer init ok");
77 | return replayer;
78 | }
79 |
80 | int replay(struct replayer* replayer, int repeatTimes)
81 | {
82 | int time;
83 | for (time = 0; time < repeatTimes && replayer->is_replaying; time++)
84 | {
85 | sleep(1);
86 | struct timeval tdiff;
87 | struct input_event event;
88 | int i, outputdev;
89 | timerclear(&tdiff);
90 | LOGD("num events: %d", replayer->num_events);
91 | for (i = 0; i < replayer->num_events && replayer->is_replaying; i++)
92 | {
93 | struct timeval now, tevent, tsleep;
94 |
95 | if (read(replayer->in_fd, &outputdev, sizeof(outputdev)) != sizeof(outputdev)
96 | || read(replayer->in_fd, &event, sizeof(event)) != sizeof(event))
97 | {
98 | LOGD("Input read error\n");
99 | return 1;
100 | }
101 |
102 | gettimeofday(&now, NULL);
103 | if (!timerisset(&tdiff))
104 | {
105 | timersub(&now, &event.time, &tdiff);
106 | }
107 |
108 | timeradd(&event.time, &tdiff, &tevent);
109 | timersub(&tevent, &now, &tsleep);
110 | if (tsleep.tv_sec > 0 || tsleep.tv_usec > 100)
111 | select(0, NULL, NULL, NULL, &tsleep);
112 |
113 | event.time = tevent;
114 | if (write(replayer->out_fds[outputdev], &event, sizeof(event)) != sizeof(event))
115 | {
116 | LOGD("Output write error\n");
117 | return 2;
118 | }
119 | }
120 | replayer->in_fd = close(replayer->in_fd);
121 | LOGD("open file: %s", replayer->file_path);
122 | if ((replayer->in_fd = open(replayer->file_path, O_RDONLY)) < 0) {
123 | LOGD("Couldn't open input\n");
124 | return 3;
125 | }
126 |
127 | }
128 | return 0;
129 | }
130 |
131 | void closeFiles(struct replayer* replayer)
132 | {
133 | int result = 0, i;
134 | for (i = 0; i < NUM_DEVICES; i++)
135 | {
136 | if (replayer->out_fds[i] > 0)
137 | {
138 | result += close(replayer->out_fds[i]);
139 | }
140 | }
141 | if (replayer->in_fd > 0)
142 | {
143 | result += close(replayer->in_fd);
144 | }
145 | LOGD("close file: %d", result);
146 | }
147 |
148 | void destroy(struct replayer* replayer)
149 | {
150 | closeFiles(replayer);
151 | free(replayer->out_fds);
152 | free(replayer);
153 | LOGD("replayer destroy");
154 | }
155 |
156 | void stop(struct replayer* replayer)
157 | {
158 | replayer->is_replaying = 0;
159 | }
160 | #endif /* REPLAYER_H_ */
161 |
--------------------------------------------------------------------------------
/jni/uinput.h:
--------------------------------------------------------------------------------
1 | /* Android munges Linux headers to avoid copyright issues, but doesn't munge linux/uinput.h,
2 | * so constants reproduced here.
3 | */
4 |
5 | #ifndef __UINPUT__
6 | #define __UINPUT__
7 |
8 |
9 | #define UI_SET_EVBIT 0x40045564
10 | #define UI_SET_KEYBIT 0x40045565
11 | #define UI_SET_RELBIT 0x40045566
12 | #define UI_SET_ABSBIT 0x40045567
13 |
14 | #define UINPUT_MAX_NAME_SIZE 80
15 |
16 | struct uinput_id {
17 | uint16_t bustype;
18 | uint16_t vendor;
19 | uint16_t product;
20 | uint16_t version;
21 | };
22 |
23 | struct uinput_dev {
24 | char name[UINPUT_MAX_NAME_SIZE];
25 | struct uinput_id id;
26 | int ff_effects_max;
27 | int absmax[ABS_MAX + 1];
28 | int absmin[ABS_MAX + 1];
29 | int absfuzz[ABS_MAX + 1];
30 | int absflat[ABS_MAX + 1];
31 | };
32 |
33 | struct uinput_event {
34 | struct timeval time;
35 | uint16_t type;
36 | uint16_t code;
37 | int32_t value;
38 | };
39 |
40 |
41 | #endif /* __UINPUT__ */
42 |
--------------------------------------------------------------------------------
/libs/android-support-v4.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/libs/android-support-v4.jar
--------------------------------------------------------------------------------
/libs/armeabi/libinputrecorder.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/libs/armeabi/libinputrecorder.so
--------------------------------------------------------------------------------
/libs/armeabi/libinputreplayer.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/libs/armeabi/libinputreplayer.so
--------------------------------------------------------------------------------
/lint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/obj/local/armeabi/libinputrecorder.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/obj/local/armeabi/libinputrecorder.so
--------------------------------------------------------------------------------
/obj/local/armeabi/libinputreplayer.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/obj/local/armeabi/libinputreplayer.so
--------------------------------------------------------------------------------
/proguard-project.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/project.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system edit
7 | # "ant.properties", and override values to adapt the script to your
8 | # project structure.
9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 |
13 | # Project target.
14 | target=android-19
15 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/res/drawable-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/res/drawable-ldpi/.gitignore
--------------------------------------------------------------------------------
/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/res/drawable-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xiaobinlzy/touchRepeater_Android/f40a7652867888e83b4691867a04388a04407d3d/res/drawable-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
--------------------------------------------------------------------------------
/res/layout/repeat_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
20 |
21 |
--------------------------------------------------------------------------------
/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 16dp
5 | 16dp
6 |
7 |
8 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | TouchRepeater
5 | Hello world!
6 | Settings
7 | 开始录制
8 | 停止录制
9 | 开始回放
10 | 停止回放
11 |
12 |
--------------------------------------------------------------------------------
/src/com/dll/touchrepeater/InputRecorder.java:
--------------------------------------------------------------------------------
1 | package com.dll.touchrepeater;
2 |
3 | import android.os.Handler;
4 | import android.os.Handler.Callback;
5 | import android.os.Looper;
6 | import android.os.Message;
7 | import android.util.Log;
8 |
9 | public class InputRecorder implements Runnable {
10 |
11 | static {
12 | System.loadLibrary("inputrecorder");
13 | }
14 |
15 | private static final String TAG = InputRecorder.class.getName();
16 |
17 | protected boolean isRecording;
18 |
19 | protected String recordFilePath;
20 |
21 | protected Handler handler;
22 |
23 | protected InputRecorderListener listener;
24 |
25 | private static InputRecorder instance;
26 |
27 | protected long recorderPointer;
28 |
29 | private InputRecorder() {
30 | super();
31 | isRecording = false;
32 | handler = new Handler(Looper.getMainLooper(), new RecorderHandle());
33 | }
34 |
35 | public static InputRecorder getInstance() {
36 | if (instance == null) {
37 | instance = new InputRecorder();
38 | }
39 | return instance;
40 | }
41 |
42 | /**
43 | * 是否正在录制输入事件
44 | *
45 | * @return
46 | */
47 | public boolean isRecording() {
48 | return isRecording;
49 | }
50 |
51 | /**
52 | * 开始录制输入事件。
53 | *
54 | * @param filePath
55 | * 存储的文件路径
56 | * @return 是否成功,如果失败可能是未获取root权限。只有当返回true时会调用回调接口,返回false不会回调。
57 | */
58 | public synchronized boolean record(String recordFilePath) {
59 | if (isRecording) {
60 | return true;
61 | }
62 | long pointer;
63 | if ((pointer = nativeInit(recordFilePath)) != 0) {
64 | isRecording = true;
65 | this.recordFilePath = recordFilePath;
66 | recorderPointer = pointer;
67 | new Thread(this).start();
68 | return true;
69 | } else {
70 | return false;
71 | }
72 | }
73 |
74 | /**
75 | * 停止录制输入事件
76 | *
77 | */
78 | public void stop() {
79 | if (isRecording) {
80 | nativeStop(recorderPointer);
81 | isRecording = false;
82 | Log.i(TAG, "record stop");
83 | handler.obtainMessage(RecorderHandle.WHAT_FINISH).sendToTarget();
84 | }
85 | }
86 |
87 | private native void nativeStop(long recorderPointer);
88 |
89 | private native int nativeRecord(long recorderPointer);
90 |
91 | private native long nativeInit(String filePath);
92 |
93 |
94 | @Override
95 | public void run() {
96 | if (nativeRecord(recorderPointer) == 0) {
97 | Log.i(TAG, "record thread end");
98 | } else {
99 | Log.i(TAG, "record thread failed");
100 | isRecording = false;
101 | handler.obtainMessage(RecorderHandle.WHAT_FAILED).sendToTarget();
102 | }
103 | }
104 |
105 | public void setRecorderListener(InputRecorderListener listener) {
106 | this.listener = listener;
107 | }
108 |
109 | /**
110 | * InputRecorder的回调接口。
111 | *
112 | * @author DLL email: xiaobinlzy@163.com
113 | *
114 | */
115 | public static interface InputRecorderListener {
116 |
117 | /**
118 | * 录制完成后调用
119 | *
120 | * @param recorder
121 | * @param filePath
122 | */
123 | public void onRecorderFinish(InputRecorder recorder, String filePath);
124 |
125 | /**
126 | * 录制失败后调用。
127 | *
128 | * @param recorder
129 | * @param filePath
130 | */
131 | public void onRecorderFailed(InputRecorder recorder, String filePath);
132 | }
133 |
134 | private class RecorderHandle implements Callback {
135 |
136 | private static final int WHAT_FINISH = 0;
137 | private static final int WHAT_FAILED = 1;
138 |
139 | @Override
140 | public boolean handleMessage(Message msg) {
141 | if (listener == null) {
142 | return false;
143 | }
144 | switch (msg.what) {
145 | case WHAT_FINISH:
146 | listener.onRecorderFinish(InputRecorder.this, recordFilePath);
147 | break;
148 | case WHAT_FAILED:
149 | listener.onRecorderFailed(InputRecorder.this, recordFilePath);
150 | break;
151 | }
152 | return false;
153 | }
154 |
155 | }
156 |
157 | }
158 |
--------------------------------------------------------------------------------
/src/com/dll/touchrepeater/InputReplayer.java:
--------------------------------------------------------------------------------
1 | package com.dll.touchrepeater;
2 |
3 | import android.os.Handler;
4 | import android.os.Handler.Callback;
5 | import android.os.Looper;
6 | import android.os.Message;
7 | import android.util.Log;
8 |
9 | public class InputReplayer implements Runnable {
10 |
11 | static {
12 | System.loadLibrary("inputreplayer");
13 | }
14 |
15 | private static final String TAG = InputReplayer.class.getName();
16 |
17 | protected boolean isReplaying;
18 |
19 | protected String replayFilePath;
20 |
21 | protected long replayerPointer;
22 |
23 | private static InputReplayer instance;
24 |
25 | protected InputReplayerListener listener;
26 |
27 | protected Handler handler;
28 |
29 | protected int repeatTimes;
30 |
31 | private InputReplayer() {
32 | super();
33 | isReplaying = false;
34 | handler = new Handler(Looper.getMainLooper(), new ReplayerHandler());
35 | }
36 |
37 | public static InputReplayer getInstance() {
38 | if (instance == null) {
39 | instance = new InputReplayer();
40 | }
41 | return instance;
42 | }
43 |
44 | public boolean isReplaying() {
45 | return isReplaying;
46 | }
47 |
48 | public synchronized boolean replay(String replayFilePath, int repeatTimes) {
49 | if (isReplaying) {
50 | return true;
51 | }
52 | long pointer;
53 | if ((pointer = nativeInit(replayFilePath)) != 0) {
54 | isReplaying = true;
55 | replayerPointer = pointer;
56 | this.replayFilePath = replayFilePath;
57 | this.repeatTimes = repeatTimes;
58 | new Thread(this).start();
59 | return true;
60 | } else {
61 | Log.i(TAG, "replay init failed");
62 | return false;
63 | }
64 | }
65 |
66 | public synchronized void stop() {
67 | if (isReplaying) {
68 | nativeStop(replayerPointer);
69 | isReplaying = false;
70 | Log.i(TAG, "replay stop");
71 | handler.obtainMessage(ReplayerHandler.WHAT_FINISH).sendToTarget();
72 | }
73 | }
74 |
75 | public void setReplayerListener(InputReplayerListener listener) {
76 | this.listener = listener;
77 | }
78 |
79 | private native long nativeInit(String replayFilePath);
80 |
81 | private native int nativeReplay(long replayerPointer, int repeatTimes);
82 |
83 | private native void nativeStop(long replayerPointer);
84 |
85 | @Override
86 | public void run() {
87 | if (nativeReplay(replayerPointer, repeatTimes) == 0) {
88 | Log.i(TAG, "replay thread end");
89 | handler.obtainMessage(ReplayerHandler.WHAT_FINISH).sendToTarget();
90 | } else {
91 | Log.i(TAG, "replay thread intercept");
92 | handler.obtainMessage(ReplayerHandler.WHAT_FAILED).sendToTarget();
93 | }
94 | isReplaying = false;
95 | }
96 |
97 | public static interface InputReplayerListener {
98 |
99 | /**
100 | * 当回放完成时调用
101 | *
102 | * @param replayer
103 | * @param filePath
104 | */
105 | public void onReplayerFinish(InputReplayer replayer, String filePath);
106 |
107 | /**
108 | * 当回放失败时调用
109 | *
110 | * @param replayer
111 | * @param filePath
112 | */
113 | public void onReplayerFailed(InputReplayer replayer, String filePath);
114 | }
115 |
116 | private class ReplayerHandler implements Callback {
117 | private static final int WHAT_FINISH = 0;
118 | private static final int WHAT_FAILED = 1;
119 |
120 | @Override
121 | public boolean handleMessage(Message msg) {
122 | if (listener == null) {
123 | return false;
124 | }
125 | switch (msg.what) {
126 | case WHAT_FINISH:
127 | listener.onReplayerFinish(InputReplayer.this, replayFilePath);
128 | break;
129 | case WHAT_FAILED:
130 | listener.onReplayerFailed(InputReplayer.this, replayFilePath);
131 | break;
132 | }
133 | return false;
134 | }
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/src/com/dll/touchrepeater/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.dll.touchrepeater;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 |
6 | public class MainActivity extends Activity {
7 |
8 |
9 | @Override
10 | protected void onCreate(Bundle savedInstanceState) {
11 | super.onCreate(savedInstanceState);
12 | setContentView(R.layout.activity_main);
13 |
14 | RepeatService.startService(this);
15 | }
16 |
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/com/dll/touchrepeater/RepeatLayout.java:
--------------------------------------------------------------------------------
1 | package com.dll.touchrepeater;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 | import android.widget.LinearLayout;
7 |
8 | public class RepeatLayout extends LinearLayout {
9 |
10 | protected OnEventListener eventListener;
11 |
12 | public RepeatLayout(Context context) {
13 | super(context);
14 | }
15 |
16 | public RepeatLayout(Context context, AttributeSet attrs) {
17 | super(context, attrs);
18 | }
19 |
20 | public void setEventListener(OnEventListener listener) {
21 | this.eventListener = listener;
22 | }
23 |
24 | @Override
25 | public boolean dispatchTouchEvent(MotionEvent ev) {
26 | if (this.eventListener != null) {
27 | this.eventListener.onDispatchTouchEvent(ev);
28 | }
29 | return super.dispatchTouchEvent(ev);
30 | }
31 |
32 | public static interface OnEventListener {
33 | public void onDispatchTouchEvent(MotionEvent ev);
34 |
35 | public boolean onInterceptTouchEvent(MotionEvent ev);
36 | }
37 |
38 | @Override
39 | public boolean onInterceptTouchEvent(MotionEvent ev) {
40 | if (this.eventListener == null) {
41 | return super.onInterceptTouchEvent(ev);
42 | } else {
43 | return this.eventListener.onInterceptTouchEvent(ev);
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/com/dll/touchrepeater/RepeatService.java:
--------------------------------------------------------------------------------
1 | package com.dll.touchrepeater;
2 |
3 | import java.io.File;
4 |
5 | import android.app.Notification;
6 | import android.app.Service;
7 | import android.content.Context;
8 | import android.content.Intent;
9 | import android.graphics.PixelFormat;
10 | import android.os.IBinder;
11 | import android.util.Log;
12 | import android.view.Gravity;
13 | import android.view.LayoutInflater;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 | import android.view.View.OnClickListener;
17 | import android.view.WindowManager;
18 | import android.view.WindowManager.LayoutParams;
19 | import android.widget.Button;
20 | import android.widget.RemoteViews;
21 | import android.widget.Toast;
22 |
23 | import com.dll.touchrepeater.InputRecorder.InputRecorderListener;
24 | import com.dll.touchrepeater.InputReplayer.InputReplayerListener;
25 | import com.dll.touchrepeater.RepeatLayout.OnEventListener;
26 | import com.dll.util.FilePathUtil;
27 |
28 | public class RepeatService extends Service {
29 |
30 | private static final String TAG = RepeatService.class.getName();
31 |
32 | protected InputRecorder recorder;
33 |
34 | protected InputReplayer replayer;
35 |
36 | protected RepeatLayout layout;
37 |
38 | protected LayoutParams layoutParams;
39 |
40 | protected WindowManager manager;
41 |
42 | protected boolean isAddedToWindow;
43 |
44 | protected Button btnRecord, btnReplay;
45 |
46 | protected static final String RECORD_FILE_NAME = "record_events";
47 |
48 | protected static final String RECORD_FOLDER = "record";
49 |
50 | protected String recordFilePath;
51 |
52 | protected float originX, originY, startX, startY;
53 |
54 | protected boolean isInterceptEvent;
55 |
56 | protected ServiceListener listener;
57 |
58 | private static final int SERVICE_ID = 1023;
59 |
60 | protected Notification notification;
61 |
62 | @Override
63 | public void onCreate() {
64 | super.onCreate();
65 | listener = new ServiceListener();
66 | String[] commands = new String[8];
67 | for (int i = 0; i < commands.length; i++) {
68 | commands[i] = "chmod 777 /dev/input/event" + i + "\n";
69 | }
70 | RootPermission.rootPermission(commands);
71 | recorder = InputRecorder.getInstance();
72 | replayer = InputReplayer.getInstance();
73 | recordFilePath = FilePathUtil.makeFilePath(this, RECORD_FOLDER, RECORD_FILE_NAME);
74 | initLayout();
75 | recorder.setRecorderListener(listener);
76 | replayer.setReplayerListener(listener);
77 |
78 | notification = new Notification();
79 | notification.flags = Notification.FLAG_NO_CLEAR;
80 | notification.tickerText = "记录操作";
81 | RemoteViews remoteViews = new RemoteViews(getPackageName(), android.R.layout.simple_gallery_item);
82 | remoteViews.setTextViewText(android.R.id.text1, "记录操作");
83 | notification.contentView = remoteViews;
84 | }
85 |
86 | @Override
87 | public int onStartCommand(Intent intent, int flags, int startId) {
88 | addToWindow();
89 | startForeground(SERVICE_ID, notification);
90 | return super.onStartCommand(intent, flags, startId);
91 | }
92 |
93 | @Override
94 | public void onDestroy() {
95 | Log.i(TAG, "repeat service destroy");
96 | super.onDestroy();
97 | }
98 |
99 | @Override
100 | public IBinder onBind(Intent intent) {
101 | return null;
102 | }
103 |
104 | protected void onClickRecord() {
105 | if (!recorder.isRecording()) {
106 | if (recorder.record(recordFilePath)) {
107 | btnRecord.setText(R.string.button_stopRecording);
108 | btnReplay.setEnabled(false);
109 | } else {
110 | Toast.makeText(this, "录制失败,请检查root权限", Toast.LENGTH_SHORT).show();
111 | }
112 | } else {
113 | recorder.stop();
114 | btnRecord.setText(R.string.button_startRecording);
115 | }
116 | }
117 |
118 | protected void onClickReplay() {
119 | if (!replayer.isReplaying()) {
120 | if (replayer.replay(recordFilePath, 1)) {
121 | btnReplay.setText(R.string.button_stopReplaying);
122 | btnRecord.setEnabled(false);
123 | } else {
124 | Toast.makeText(this, "重放失败,请检查文件是否正确", Toast.LENGTH_SHORT).show();
125 | }
126 | } else {
127 | replayer.stop();
128 | btnReplay.setText(R.string.button_startReplaying);
129 | }
130 | }
131 |
132 | protected void initLayout() {
133 | layoutParams = new WindowManager.LayoutParams();
134 | layout = (RepeatLayout) LayoutInflater.from(this).inflate(R.layout.repeat_layout, null);
135 | layout.setEventListener(listener);
136 | manager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);
137 | layoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;
138 | layoutParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;
139 | layoutParams.format = PixelFormat.RGBA_8888;
140 | layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
141 | layoutParams.x = 0;
142 | layoutParams.y = 0;
143 | layoutParams.width = LayoutParams.WRAP_CONTENT;
144 | layoutParams.height = LayoutParams.WRAP_CONTENT;
145 | layout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
146 | View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
147 | btnRecord = (Button) layout.findViewById(R.id.button_record);
148 | btnReplay = (Button) layout.findViewById(R.id.button_replay);
149 | btnRecord.setOnClickListener(listener);
150 | btnReplay.setOnClickListener(listener);
151 | if (!new File(recordFilePath).isFile()) {
152 | btnReplay.setEnabled(false);
153 | }
154 | }
155 |
156 | protected void addToWindow() {
157 | if (isAddedToWindow) {
158 | return;
159 | }
160 | isAddedToWindow = true;
161 | manager.addView(layout, layoutParams);
162 | }
163 |
164 | public static void startService(Context context) {
165 | Intent intent = new Intent(context, RepeatService.class);
166 | context.startService(intent);
167 | }
168 |
169 | private class ServiceListener implements OnClickListener, OnEventListener,
170 | InputRecorderListener, InputReplayerListener {
171 |
172 | @Override
173 | public void onClick(View v) {
174 | Log.i(TAG, "on click");
175 | switch (v.getId()) {
176 | case R.id.button_record:
177 | onClickRecord();
178 | break;
179 | case R.id.button_replay:
180 | onClickReplay();
181 | break;
182 | }
183 | }
184 |
185 | @Override
186 | public void onDispatchTouchEvent(MotionEvent ev) {
187 | }
188 |
189 | @Override
190 | public void onReplayerFinish(InputReplayer replayer, String filePath) {
191 | btnRecord.setEnabled(true);
192 | btnReplay.setText(R.string.button_startReplaying);
193 | }
194 |
195 | @Override
196 | public void onReplayerFailed(InputReplayer replayer, String filePath) {
197 | btnRecord.setEnabled(true);
198 | Toast.makeText(RepeatService.this, "重放失败", Toast.LENGTH_SHORT).show();
199 | btnReplay.setText(R.string.button_startReplaying);
200 | }
201 |
202 | @Override
203 | public void onRecorderFinish(InputRecorder recorder, String filePath) {
204 | btnReplay.setEnabled(true);
205 | }
206 |
207 | @Override
208 | public void onRecorderFailed(InputRecorder recorder, String filePath) {
209 | btnReplay.setEnabled(true);
210 | Toast.makeText(RepeatService.this, "录制失败", Toast.LENGTH_SHORT).show();
211 | }
212 |
213 | @Override
214 | public boolean onInterceptTouchEvent(MotionEvent event) {
215 | switch (event.getAction()) {
216 | case MotionEvent.ACTION_DOWN:
217 | startX = event.getRawX();
218 | startY = event.getRawY();
219 | originX = layoutParams.x;
220 | originY = layoutParams.y;
221 | isInterceptEvent = false;
222 | break;
223 | case MotionEvent.ACTION_UP:
224 | default:
225 | layoutParams.x = Math.round(event.getRawX() - startX + originX);
226 | layoutParams.y = Math.round(event.getRawY() - startY + originY);
227 | isInterceptEvent = isInterceptEvent || Math.abs(event.getRawX() - startX) > 2
228 | || Math.abs(event.getRawY() - startY) > 2;
229 | break;
230 | }
231 | manager.updateViewLayout(layout, layoutParams);
232 | return event.getAction() == MotionEvent.ACTION_UP ? isInterceptEvent : false;
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/src/com/dll/touchrepeater/RootPermission.java:
--------------------------------------------------------------------------------
1 | package com.dll.touchrepeater;
2 |
3 | import java.io.BufferedWriter;
4 | import java.io.IOException;
5 | import java.io.OutputStreamWriter;
6 |
7 | public class RootPermission {
8 |
9 | public static void rootPermission(String... command) {
10 | try {
11 | Process process = Runtime.getRuntime().exec("su");
12 | if (command != null && command.length > 0) {
13 | BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(
14 | process.getOutputStream()));
15 | for (int i = 0; i < command.length; i++) {
16 | writer.write(command[i]);
17 | }
18 | writer.flush();
19 | writer.close();
20 | }
21 | } catch (IOException e) {
22 | e.printStackTrace();
23 | }
24 |
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/com/dll/util/FilePathUtil.java:
--------------------------------------------------------------------------------
1 | package com.dll.util;
2 |
3 | import java.io.File;
4 |
5 | import android.content.Context;
6 |
7 | /**
8 | * 文件路径的获取和拼接
9 | *
10 | * @author DLL email: xiaobinlzy@163.com
11 | *
12 | */
13 | public class FilePathUtil {
14 |
15 | /**
16 | * 生成存储文件的路径,如果有sd卡则获取sd卡路径,否则获取应用缓存区路径。
17 | *
18 | * @param context
19 | * 应用Context
20 | * @param folderPath
21 | * 文件夹路径
22 | * @param fileName
23 | * 文件名
24 | * @return 生成的文件路径
25 | */
26 | public static String makeFilePath(Context context, String folderPath, String fileName) {
27 | File file = null;
28 | if (android.os.Environment.getExternalStorageState().equals(
29 | android.os.Environment.MEDIA_MOUNTED)) {
30 | file = new File(android.os.Environment.getExternalStorageDirectory(),
31 | folderPath);
32 | } else {
33 | file = context.getApplicationContext().getCacheDir();
34 | }
35 | if (!file.exists() || !file.isDirectory()) {
36 | file.mkdirs();
37 | }
38 | StringBuilder absoluteFolderPath = new StringBuilder(file.getAbsolutePath());
39 | if (!absoluteFolderPath.toString().endsWith("/")) {
40 | absoluteFolderPath.append("/");
41 | }
42 | if (fileName != null) {
43 | absoluteFolderPath.append(fileName);
44 | }
45 | return absoluteFolderPath.toString();
46 | }
47 |
48 | public static void clearFilePath(Context context, File filePath) {
49 | if (!filePath.exists()) {
50 | return;
51 | }
52 | if (filePath.isFile()) {
53 | filePath.delete();
54 | return;
55 | }
56 | if (filePath.isDirectory()) {
57 | File[] folders = filePath.listFiles();
58 | for (int i = 0; i < folders.length; i++) {
59 | clearFilePath(context, folders[i]);
60 | }
61 | }
62 | }
63 |
64 | }
65 |
--------------------------------------------------------------------------------