1
0
Fork 0
mirror of https://github.com/openstf/stf synced 2025-10-05 19:42:01 +02:00

Refactor InputAgent into STFService. Support copypasting (only pasting implemented in the UI right now though).

This commit is contained in:
Simo Kinnunen 2014-04-01 11:47:20 +09:00
parent 514fc3554d
commit 09eb8c539d
13 changed files with 379 additions and 50 deletions

View file

@ -347,6 +347,24 @@ module.exports = function(options) {
])
})
// Transactions
.on('clipboard.paste', function(channel, responseChannel, data) {
joinChannel(responseChannel)
push.send([
channel
, wireutil.envelope(new wire.PasteMessage(
data.text
))
])
})
.on('clipboard.copy', function(channel, responseChannel, data) {
joinChannel(responseChannel)
push.send([
channel
, wireutil.envelope(new wire.CopyMessage(
data.text
))
])
})
.on('device.identify', function(channel, responseChannel) {
push.send([
channel

View file

@ -18,6 +18,7 @@ module.exports = function(options) {
.dependency(require('./device/plugins/display'))
.dependency(require('./device/plugins/http'))
.dependency(require('./device/plugins/input'))
.dependency(require('./device/plugins/clipboard'))
.dependency(require('./device/plugins/logcat'))
.dependency(require('./device/plugins/shell'))
.dependency(require('./device/plugins/touch'))

View file

@ -0,0 +1,70 @@
var syrup = require('syrup')
var logger = require('../../../util/logger')
var wire = require('../../../wire')
var wireutil = require('../../../wire/util')
module.exports = syrup.serial()
.dependency(require('../support/router'))
.dependency(require('../support/push'))
.dependency(require('./input'))
.define(function(options, router, push, input) {
var log = logger.createLogger('device:plugins:clipboard')
router.on(wire.PasteMessage, function(channel, message) {
log.info('Pasting "%s" to clipboard', message.text)
var seq = 0
input.paste(message.text)
.then(function() {
push.send([
channel
, wireutil.envelope(new wire.TransactionDoneMessage(
options.serial
, seq++
, true
))
])
})
.catch(function(err) {
log.error('Paste failed', err.stack)
push.send([
channel
, wireutil.envelope(new wire.TransactionDoneMessage(
options.serial
, seq++
, false
, err.message
))
])
})
})
router.on(wire.CopyMessage, function(channel) {
log.info('Copying clipboard contents')
var seq = 0
input.copy()
.then(function(content) {
push.send([
channel
, wireutil.envelope(new wire.TransactionDoneMessage(
options.serial
, seq++
, true
, content
))
])
})
.catch(function(err) {
log.error('Copy failed', err.stack)
push.send([
channel
, wireutil.envelope(new wire.TransactionDoneMessage(
options.serial
, seq++
, false
, err.message
))
])
})
})
})

View file

@ -2,7 +2,7 @@ var util = require('util')
var syrup = require('syrup')
var split = require('split')
var ByteBuffer = require('protobufjs/node_modules/bytebuffer')
var Promise = require('bluebird')
var wire = require('../../../wire')
var wireutil = require('../../../wire/util')
@ -10,26 +10,29 @@ var devutil = require('../../../util/devutil')
var keyutil = require('../../../util/keyutil')
var streamutil = require('../../../util/streamutil')
var logger = require('../../../util/logger')
var ms = require('../../../wire/messagestream')
module.exports = syrup.serial()
.dependency(require('../support/adb'))
.dependency(require('../support/router'))
.dependency(require('../support/push'))
.dependency(require('../support/quit'))
.dependency(require('../resources/inputagent'))
.dependency(require('../resources/service'))
.define(function(options, adb, router, push, quit, apk) {
var log = logger.createLogger('device:plugins:input')
var serviceQueue = []
var agent = {
socket: null
, writer: null
, port: 1090
}
var service = {
socket: null
, writer: null
, reader: null
, port: 1100
, startAction: 'jp.co.cyberagent.stf.input.agent.InputService.ACTION_START'
, stopAction: 'jp.co.cyberagent.stf.input.agent.InputService.ACTION_STOP'
}
function openAgent() {
@ -40,8 +43,7 @@ module.exports = syrup.serial()
})
.then(function() {
return adb.shell(options.serial, util.format(
"export CLASSPATH='%s';" +
" exec app_process /system/bin '%s'"
"export CLASSPATH='%s'; exec app_process /system/bin '%s'"
, apk.path
, apk.main
))
@ -65,6 +67,8 @@ module.exports = syrup.serial()
})
.then(function(conn) {
agent.socket = conn
agent.writer = new ms.DelimitingStream()
agent.writer.pipe(conn)
conn.on('error', function(err) {
log.fatal('InputAgent socket had an error', err.stack)
quit.fatal()
@ -137,18 +141,30 @@ module.exports = syrup.serial()
return devutil.waitForPortToFree(adb, options.serial, service.port)
})
.then(function() {
return callService(util.format("-a '%s'", service.startAction))
return callService(util.format("-a '%s'", apk.startAction))
})
.then(function() {
return devutil.waitForPort(adb, options.serial, service.port)
})
.then(function(conn) {
service.socket = conn
service.reader = conn.pipe(new ms.DelimitedStream())
service.reader.on('data', function(data) {
if (serviceQueue.length) {
var resolver = serviceQueue.shift()
resolver.resolve(data)
}
else {
log.warn('Unexpected data from service', data)
}
})
service.writer = new ms.DelimitingStream()
service.writer.pipe(conn)
conn.on('error', function(err) {
log.fatal('InputService socket had an error', err.stack)
quit.fatal()
})
conn.on('end', function() {
.on('end', function() {
log.fatal('InputService socket ended')
quit.fatal()
})
@ -156,43 +172,135 @@ module.exports = syrup.serial()
}
function stopService() {
return callService(util.format("-a '%s'", service.stopAction))
return callService(util.format("-a '%s'", apk.stopAction))
}
function sendInputEvent(event) {
var lengthBuffer = new ByteBuffer()
, messageBuffer = new apk.proto.InputEvent(event).encode()
agent.writer.write(new apk.agentProto.InputEvent(event).encodeNB())
}
// Delimiter
lengthBuffer.writeVarint32(messageBuffer.length)
agent.socket.write(Buffer.concat([
lengthBuffer.toBuffer()
, messageBuffer.toBuffer()
]))
function version() {
return runServiceCommand(
apk.serviceProto.RequestType.VERSION
, new apk.serviceProto.VersionRequest()
)
.then(function(data) {
var response = apk.serviceProto.VersionResponse.decode(data)
if (response.success) {
return response.version
}
throw new Error('Unable to retrieve version')
})
}
function unlock() {
service.socket.write('unlock\n')
return runServiceCommand(
apk.serviceProto.RequestType.SET_KEYGUARD_STATE
, new apk.serviceProto.SetKeyguardStateRequest(false)
)
.then(function(data) {
var response = apk.serviceProto.SetKeyguardStateResponse.decode(data)
if (!response.success) {
throw new Error('Unable to unlock device')
}
})
}
function lock() {
service.socket.write('lock\n')
return runServiceCommand(
apk.serviceProto.RequestType.SET_KEYGUARD_STATE
, new apk.serviceProto.SetKeyguardStateRequest(true)
)
.then(function(data) {
var response = apk.serviceProto.SetKeyguardStateResponse.decode(data)
if (!response.success) {
throw new Error('Unable to lock device')
}
})
}
function acquireWakeLock() {
service.socket.write('acquire wake lock\n')
return runServiceCommand(
apk.serviceProto.RequestType.SET_WAKE_LOCK
, new apk.serviceProto.SetWakeLockRequest(true)
)
.then(function(data) {
var response = apk.serviceProto.SetWakeLockResponse.decode(data)
if (!response.success) {
throw new Error('Unable to acquire WakeLock')
}
})
}
function releaseWakeLock() {
service.socket.write('release wake lock\n')
return runServiceCommand(
apk.serviceProto.RequestType.SET_WAKE_LOCK
, new apk.serviceProto.SetWakeLockRequest(false)
)
.then(function(data) {
var response = apk.serviceProto.SetWakeLockResponse.decode(data)
if (!response.success) {
throw new Error('Unable to release WakeLock')
}
})
}
function identity() {
service.socket.write(util.format(
'show identity %s\n'
, options.serial
))
return runServiceCommand(
apk.serviceProto.RequestType.IDENTIFY
, new apk.serviceProto.IdentifyRequest(options.serial)
)
.then(function(data) {
var response = apk.serviceProto.IdentifyResponse.decode(data)
if (!response.success) {
throw new Error('Unable to identify device')
}
})
}
function setClipboard(text) {
return runServiceCommand(
apk.serviceProto.RequestType.SET_CLIPBOARD
, new apk.serviceProto.SetClipboardRequest(
apk.serviceProto.ClipboardType.TEXT
, text
)
)
.then(function(data) {
var response = apk.serviceProto.SetClipboardResponse.decode(data)
if (!response.success) {
throw new Error('Unable to set clipboard')
}
})
}
function getClipboard() {
return runServiceCommand(
apk.serviceProto.RequestType.GET_CLIPBOARD
, new apk.serviceProto.GetClipboardRequest(
apk.serviceProto.ClipboardType.TEXT
)
)
.then(function(data) {
var response = apk.serviceProto.GetClipboardResponse.decode(data)
if (response.success) {
switch (response.type) {
case apk.serviceProto.ClipboardType.TEXT:
return response.text
}
}
throw new Error('Unable to get clipboard')
})
}
function runServiceCommand(type, cmd) {
var resolver = Promise.defer()
service.writer.write(new apk.serviceProto.RequestEnvelope(
type
, cmd.encodeNB()
).encodeNB())
serviceQueue.push(resolver)
return resolver.promise
}
return openAgent()
@ -242,6 +350,17 @@ module.exports = syrup.serial()
, acquireWakeLock: acquireWakeLock
, releaseWakeLock: releaseWakeLock
, identity: identity
, paste: function(text) {
return setClipboard(text)
.then(function() {
sendInputEvent({
action: 2
, keyCode: adb.Keycode.KEYCODE_V
, ctrlKey: true
})
})
}
, copy: getClipboard
}
})
})

View file

@ -11,16 +11,21 @@ var logger = require('../../../util/logger')
module.exports = syrup.serial()
.dependency(require('../support/adb'))
.define(function(options, adb) {
var log = logger.createLogger('device:resources:inputagent')
var log = logger.createLogger('device:resources:service')
var resource = {
requiredVersion: '~0.2.0'
, pkg: 'jp.co.cyberagent.stf.input.agent'
, main: 'jp.co.cyberagent.stf.input.agent.InputAgent'
, apk: pathutil.vendor('InputAgent/InputAgent.apk')
, proto: ProtoBuf.loadProtoFile(
pathutil.vendor('InputAgent/proto/agent.proto')
).build().jp.co.cyberagent.stf.input.agent.proto
, pkg: 'jp.co.cyberagent.stf'
, main: 'jp.co.cyberagent.stf.InputAgent'
, apk: pathutil.vendor('STFService/STFService.apk')
, agentProto: ProtoBuf.loadProtoFile(
pathutil.vendor('STFService/proto/agent.proto')
).build().jp.co.cyberagent.stf.proto
, serviceProto: ProtoBuf.loadProtoFile(
pathutil.vendor('STFService/proto/service.proto')
).build().jp.co.cyberagent.stf.proto
, startAction: 'jp.co.cyberagent.stf.ACTION_START'
, stopAction: 'jp.co.cyberagent.stf.ACTION_STOP'
}
function getPath() {
@ -34,7 +39,7 @@ module.exports = syrup.serial()
}
function install() {
log.info('Checking whether we need to install InputAgent.apk')
log.info('Checking whether we need to install STFService')
return getPath()
.then(function(installedPath) {
log.info('Running version check')
@ -49,6 +54,10 @@ module.exports = syrup.serial()
.timeout(10000)
.then(function(buffer) {
var version = buffer.toString()
throw new Error(util.format(
'Incompatible version %s'
, version
))
if (semver.satisfies(version, resource.requiredVersion)) {
return installedPath
}
@ -62,7 +71,7 @@ module.exports = syrup.serial()
})
})
.catch(function() {
log.info('Installing InputAgent.apk')
log.info('Installing STFService')
return adb.install(options.serial, resource.apk)
.then(function() {
return getPath()
@ -72,12 +81,8 @@ module.exports = syrup.serial()
return install()
.then(function(path) {
log.info('InputAgent.apk up to date')
return {
path: path
, pkg: resource.pkg
, main: resource.main
, proto: resource.proto
}
log.info('STFService up to date')
resource.path = path
return resource
})
})

View file

@ -9,6 +9,7 @@ module.exports = syrup.serial()
.define(function(options) {
var log = logger.createLogger('device:support:adb')
var adb = adbkit.createClient()
adb.Keycode = adbkit.Keycode
function ensureBootComplete() {
return promiseutil.periodicNotify(

View file

@ -1,6 +1,7 @@
// Message wrapper
enum MessageType {
CopyMessage = 33;
DeviceAbsentMessage = 1;
DeviceHeartbeatMessage = 28;
DeviceIdentityMessage = 2;
@ -21,6 +22,7 @@ enum MessageType {
LaunchActivityMessage = 31;
LeaveGroupMessage = 15;
LogcatApplyFiltersMessage = 16;
PasteMessage = 32;
ProbeMessage = 17;
ShellCommandMessage = 18;
ShellKeepAliveMessage = 19;
@ -216,6 +218,13 @@ message TypeMessage {
required string text = 1;
}
message PasteMessage {
required string text = 1;
}
message CopyMessage {
}
enum KeyCode {
HOME = 3;
BACK = 4;

View file

@ -78,6 +78,20 @@ module.exports = function ControlServiceFactory(
})
}
this.paste = function(text) {
var tx = TransactionService.create(target)
socket.emit('clipboard.paste', channel, tx.channel, {
text: text
})
return tx
}
this.copy = function() {
var tx = TransactionService.create(target)
socket.emit('clipboard.copy', channel, tx.channel)
return tx
}
this.shell = function(command) {
var tx = TransactionService.create(target)
socket.emit('shell.command', channel, tx.channel, {

View file

@ -86,7 +86,7 @@ module.exports = function DeviceScreenDirective($document, ScalingService) {
function pasteListener(e) {
e.preventDefault() // no need to change value
scope.control.type(e.clipboardData.getData('text/plain'))
scope.control.paste(e.clipboardData.getData('text/plain'))
}
function maybeLoadScreen() {

Binary file not shown.

BIN
vendor/STFService/STFService.apk vendored Normal file

Binary file not shown.

View file

@ -1,4 +1,4 @@
package jp.co.cyberagent.stf.input.agent.proto;
package jp.co.cyberagent.stf.proto;
option java_outer_classname = "AgentProto";

92
vendor/STFService/proto/service.proto vendored Normal file
View file

@ -0,0 +1,92 @@
package jp.co.cyberagent.stf.proto;
option java_outer_classname = "ServiceProto";
enum RequestType {
VERSION = 0;
SET_KEYGUARD_STATE = 1;
SET_WAKE_LOCK = 2;
SET_CLIPBOARD = 3;
GET_CLIPBOARD = 4;
GET_PROPERTIES = 6;
IDENTIFY = 7;
}
message RequestEnvelope {
required RequestType type = 1;
required bytes request = 2;
}
message ResponseEnvelope {
required bool success = 1;
required bytes response = 2;
}
message VersionRequest {
}
message VersionResponse {
required bool success = 1;
optional string version = 2;
}
message SetKeyguardStateRequest {
required bool enabled = 1;
}
message SetKeyguardStateResponse {
required bool success = 1;
}
message SetWakeLockRequest {
required bool enabled = 1;
}
message SetWakeLockResponse {
required bool success = 1;
}
enum ClipboardType {
TEXT = 1;
}
message SetClipboardRequest {
required ClipboardType type = 1;
optional string text = 2;
}
message SetClipboardResponse {
required bool success = 1;
}
message GetClipboardRequest {
required ClipboardType type = 1;
}
message GetClipboardResponse {
required bool success = 1;
optional ClipboardType type = 2;
optional string text = 3;
}
message Property {
required string name = 1;
required string value = 2;
}
message GetPropertiesRequest {
repeated string properties = 1;
}
message GetPropertiesResponse {
required bool success = 1;
repeated Property properties = 2;
}
message IdentifyRequest {
required string serial = 1;
}
message IdentifyResponse {
required bool success = 1;
}