mirror of
https://github.com/DeviceFarmer/stf.git
synced 2026-04-18 08:13:31 +02:00
Rename "roles" to "units". Put units in their own folders.
This commit is contained in:
324
lib/units/device/plugins/account.js
Normal file
324
lib/units/device/plugins/account.js
Normal file
@@ -0,0 +1,324 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./service'))
|
||||
.dependency(require('./identity'))
|
||||
.dependency(require('./touch'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../support/adb'))
|
||||
.define(function(options, service, identity, touch, router, push, adb) {
|
||||
var log = logger.createLogger('device:plugins:account')
|
||||
|
||||
function checkAccount(type, account) {
|
||||
return service.getAccounts({type: type})
|
||||
.timeout(30000)
|
||||
.then(function(accounts) {
|
||||
if(accounts.indexOf(account) >= 0) {
|
||||
return true
|
||||
}
|
||||
throw new Error('The account is not added')
|
||||
})
|
||||
}
|
||||
|
||||
router.on(wire.AccountCheckMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.info('Checking if account "%s" is added',message.account)
|
||||
checkAccount(message.type, message.account)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Account check failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.AccountGetMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.info('Getting account(s)')
|
||||
|
||||
service.getAccounts(message)
|
||||
.timeout(30000)
|
||||
.then(function(accounts) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success',accounts)
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Account get failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.AccountRemoveMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.info('Removing "%s" account(s)', message.type)
|
||||
|
||||
service.removeAccount(message)
|
||||
.timeout(30000)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Account removal failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.AccountAddMenuMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.info('Showing add account menu for Google Account')
|
||||
|
||||
service.addAccountMenu()
|
||||
.timeout(30000)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Add account menu failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.AccountAddMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
var type = "com.google"
|
||||
var account = message.user + "@gmail.com";
|
||||
|
||||
log.info('Adding Google Account automatedly')
|
||||
|
||||
var version = identity.version.substring(0,3)
|
||||
|
||||
function automation() {
|
||||
switch (version) {
|
||||
case '2.3': // tested: 2.3.3-2.3.6
|
||||
return service.pressKey('dpad_down').delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('dpad_down')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(2000)
|
||||
.then(function() {
|
||||
return service.pressKey('dpad_down')
|
||||
}).delay(2000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(2000)
|
||||
.then(function() {
|
||||
return service.type(message.user)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.type(message.password)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
})
|
||||
case '4.0': // tested: 4.0.3 and 4.0.4
|
||||
return service.pressKey('tab').delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(2000)
|
||||
.then(function() {
|
||||
return service.type(message.user)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.type(message.password)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
})
|
||||
case '4.1': // tested: 4.1.1 and 4.1.2
|
||||
return service.pressKey('tab').delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(2000)
|
||||
.then(function() {
|
||||
return service.type(message.user)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.type(message.password)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
})
|
||||
case '4.2': // tested: 4.2.2
|
||||
return service.pressKey('tab').delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(2000)
|
||||
.then(function() {
|
||||
return service.type(message.user)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.type(message.password)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('tab')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('tab')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('tab')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
})
|
||||
case '4.3': // tested: 4.3
|
||||
case '4.4': // tested: 4.4.2
|
||||
default:
|
||||
return service.pressKey('tab').delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(2000)
|
||||
.then(function() {
|
||||
return service.type(message.user)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('switch_charset')
|
||||
}).delay(100)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.type(message.password)
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('tab')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('tab')
|
||||
}).delay(1000)
|
||||
.then(function() {
|
||||
return service.pressKey('enter')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// First check if the account is already added so we don't continue
|
||||
return checkAccount(type, account)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('Add account failed: account was already added')
|
||||
])
|
||||
})
|
||||
.catch(function() {
|
||||
return adb.clear(options.serial, 'com.google.android.gsf.login')
|
||||
.catch(function() {
|
||||
// The package name is different in 2.3, so let's try the old name
|
||||
// if the new name fails.
|
||||
return adb.clear(options.serial, 'com.google.android.gsf')
|
||||
})
|
||||
.then(function() {
|
||||
return service.addAccountMenu()
|
||||
})
|
||||
.delay(5000)
|
||||
.then(function() {
|
||||
// Just in case the add account menu has any button focused
|
||||
return touch.tap({x:0, y:0.9})
|
||||
})
|
||||
.delay(500)
|
||||
.then(function() {
|
||||
return automation()
|
||||
})
|
||||
.delay(3000)
|
||||
.then(function () {
|
||||
return service.pressKey('home')
|
||||
})
|
||||
.then(function () {
|
||||
return checkAccount(type, account)
|
||||
})
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Add account failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('Add account failed: ' + err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
122
lib/units/device/plugins/browser.js
Normal file
122
lib/units/device/plugins/browser.js
Normal file
@@ -0,0 +1,122 @@
|
||||
var util = require('util')
|
||||
|
||||
var syrup = require('syrup')
|
||||
|
||||
var browsers = require('stf-browser-db')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
|
||||
var mapping = (function() {
|
||||
var list = Object.create(null)
|
||||
Object.keys(browsers).forEach(function(id) {
|
||||
var browser = browsers[id]
|
||||
if (browser.platforms.android) {
|
||||
list[browser.platforms.android.package] = id
|
||||
}
|
||||
})
|
||||
return list
|
||||
})()
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('./service'))
|
||||
.define(function(options, router, push, adb, service) {
|
||||
var log = logger.createLogger('device:plugins:browser')
|
||||
|
||||
function pkg(component) {
|
||||
return component.split('/', 1)[0]
|
||||
}
|
||||
|
||||
function processApp(app) {
|
||||
var packageName = pkg(app.component)
|
||||
var browserId = mapping[packageName]
|
||||
|
||||
if (!browserId) {
|
||||
throw new Error(util.format('Unmapped browser "%s"', packageName))
|
||||
}
|
||||
|
||||
return {
|
||||
id: app.component
|
||||
, type: browserId
|
||||
, name: browsers[browserId].name
|
||||
, selected: app.selected
|
||||
, system: app.system
|
||||
}
|
||||
}
|
||||
|
||||
function updateBrowsers(data) {
|
||||
log.info('Updating browser list')
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.DeviceBrowserMessage(
|
||||
options.serial
|
||||
, data.selected
|
||||
, data.apps.map(function(app) {
|
||||
return new wire.DeviceBrowserAppMessage(processApp(app))
|
||||
})
|
||||
))
|
||||
])
|
||||
}
|
||||
|
||||
function loadBrowsers() {
|
||||
log.info('Loading browser list')
|
||||
return service.getBrowsers()
|
||||
.then(updateBrowsers)
|
||||
}
|
||||
|
||||
service.on('browserPackageChange', updateBrowsers)
|
||||
|
||||
router.on(wire.BrowserOpenMessage, function(channel, message) {
|
||||
if (message.browser) {
|
||||
log.info('Opening "%s" in "%s"', message.url, message.browser)
|
||||
}
|
||||
else {
|
||||
log.info('Opening "%s"', message.url)
|
||||
}
|
||||
|
||||
var reply = wireutil.reply(options.serial)
|
||||
adb.startActivity(options.serial, {
|
||||
action: 'android.intent.action.VIEW'
|
||||
, component: message.browser
|
||||
, data: message.url
|
||||
})
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Browser could not be opened', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail()
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.BrowserClearMessage, function(channel, message) {
|
||||
log.info('Clearing "%s"', message.browser)
|
||||
var reply = wireutil.reply(options.serial)
|
||||
adb.clear(options.serial, pkg(message.browser))
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Browser could not be cleared', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail()
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
return loadBrowsers()
|
||||
})
|
||||
51
lib/units/device/plugins/clipboard.js
Normal file
51
lib/units/device/plugins/clipboard.js
Normal file
@@ -0,0 +1,51 @@
|
||||
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('./service'))
|
||||
.define(function(options, router, push, service) {
|
||||
var log = logger.createLogger('device:plugins:clipboard')
|
||||
|
||||
router.on(wire.PasteMessage, function(channel, message) {
|
||||
log.info('Pasting "%s" to clipboard', message.text)
|
||||
var reply = wireutil.reply(options.serial)
|
||||
service.paste(message.text)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Paste failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.CopyMessage, function(channel) {
|
||||
log.info('Copying clipboard contents')
|
||||
var reply = wireutil.reply(options.serial)
|
||||
service.copy()
|
||||
.then(function(content) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay(content)
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Copy failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
111
lib/units/device/plugins/connect.js
Normal file
111
lib/units/device/plugins/connect.js
Normal file
@@ -0,0 +1,111 @@
|
||||
var util = require('util')
|
||||
|
||||
var syrup = require('syrup')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('./group'))
|
||||
.define(function(options, adb, router, push, group) {
|
||||
var log = logger.createLogger('device:plugins:connect')
|
||||
, plugin = Object.create(null)
|
||||
, activeServer = null
|
||||
|
||||
plugin.port = options.ports.pop()
|
||||
plugin.url = util.format('%s:%s', options.publicIp, plugin.port)
|
||||
|
||||
plugin.start = function() {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (plugin.isRunning()) {
|
||||
return resolve(plugin.url)
|
||||
}
|
||||
|
||||
var server = adb.createTcpUsbBridge(options.serial)
|
||||
|
||||
server.on('listening', function() {
|
||||
resolve(plugin.url)
|
||||
})
|
||||
|
||||
server.on('connection', function(conn) {
|
||||
log.info('New remote ADB connection from %s', conn.remoteAddress)
|
||||
conn.on('userActivity', function() {
|
||||
group.keepalive()
|
||||
})
|
||||
})
|
||||
|
||||
server.on('error', reject)
|
||||
|
||||
log.info(util.format('Listening on port %d', plugin.port))
|
||||
server.listen(plugin.port)
|
||||
|
||||
activeServer = server
|
||||
lifecycle.share('Remote ADB', activeServer)
|
||||
})
|
||||
}
|
||||
|
||||
plugin.stop = Promise.method(function() {
|
||||
if (plugin.isRunning()) {
|
||||
activeServer.close()
|
||||
activeServer.end()
|
||||
}
|
||||
})
|
||||
|
||||
plugin.end = Promise.method(function() {
|
||||
if (plugin.isRunning()) {
|
||||
activeServer.end()
|
||||
}
|
||||
})
|
||||
|
||||
plugin.isRunning = function() {
|
||||
return !!activeServer
|
||||
}
|
||||
|
||||
lifecycle.observe(plugin.stop)
|
||||
group.on('leave', plugin.end)
|
||||
|
||||
router
|
||||
.on(wire.ConnectStartMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.start()
|
||||
.then(function(url) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay(url)
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Unable to start remote connect service', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
.on(wire.ConnectStopMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.end()
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to stop connect service', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
return plugin.start()
|
||||
.return(plugin)
|
||||
})
|
||||
20
lib/units/device/plugins/data.js
Normal file
20
lib/units/device/plugins/data.js
Normal file
@@ -0,0 +1,20 @@
|
||||
var syrup = require('syrup')
|
||||
var deviceData = require('stf-device-db')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./identity'))
|
||||
.define(function(options, identity) {
|
||||
var log = logger.createLogger('device:plugins:data')
|
||||
|
||||
function find() {
|
||||
var data = deviceData.find(identity)
|
||||
if (!data) {
|
||||
log.warn('Unable to find device data')
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
return find()
|
||||
})
|
||||
25
lib/units/device/plugins/display.js
Normal file
25
lib/units/device/plugins/display.js
Normal file
@@ -0,0 +1,25 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./service'))
|
||||
.dependency(require('./http'))
|
||||
.define(function(options, service, http) {
|
||||
var log = logger.createLogger('device:plugins:display')
|
||||
|
||||
function fetch() {
|
||||
log.info('Fetching display info')
|
||||
return service.getDisplay(0)
|
||||
.catch(function() {
|
||||
log.info('Falling back to HTTP API')
|
||||
return http.getDisplay(0)
|
||||
})
|
||||
.then(function(display) {
|
||||
display.url = http.getDisplayUrl(display.id)
|
||||
return display
|
||||
})
|
||||
}
|
||||
|
||||
return fetch()
|
||||
})
|
||||
237
lib/units/device/plugins/forward.js
Normal file
237
lib/units/device/plugins/forward.js
Normal file
@@ -0,0 +1,237 @@
|
||||
var net = require('net')
|
||||
var util = require('util')
|
||||
|
||||
var syrup = require('syrup')
|
||||
var Promise = require('bluebird')
|
||||
var split = require('split')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var devutil = require('../../../util/devutil')
|
||||
var streamutil = require('../../../util/streamutil')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../resources/remote'))
|
||||
.define(function(options, adb, router, push, remote) {
|
||||
var log = logger.createLogger('device:plugins:forward')
|
||||
|
||||
var service = {
|
||||
port: 2810
|
||||
, privatePorts: (function() {
|
||||
var ports = []
|
||||
for (var i = 2520; i <= 2540; ++i) {
|
||||
ports.push(i)
|
||||
}
|
||||
return ports
|
||||
})()
|
||||
, forwards: Object.create(null)
|
||||
}
|
||||
|
||||
function openService() {
|
||||
log.info('Launching reverse port forwarding service')
|
||||
return devutil.ensureUnusedPort(adb, options.serial, service.port)
|
||||
.timeout(10000)
|
||||
.then(function() {
|
||||
return adb.shell(options.serial, [
|
||||
remote.bin
|
||||
, '--lib', remote.lib
|
||||
, '--listen-forward', service.port
|
||||
])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
lifecycle.share('Forward shell', out)
|
||||
streamutil.talk(log, 'Forward shell says: "%s"', out)
|
||||
})
|
||||
.then(function() {
|
||||
return devutil.waitForPort(adb, options.serial, service.port)
|
||||
.timeout(10000)
|
||||
})
|
||||
.then(function(conn) {
|
||||
conn.end()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function createForward(data) {
|
||||
log.info(
|
||||
'Reverse port forwarding port %d to %s:%d'
|
||||
, data.devicePort
|
||||
, data.targetHost
|
||||
, data.targetPort
|
||||
)
|
||||
|
||||
var forward = service.forwards[data.devicePort]
|
||||
|
||||
if (forward) {
|
||||
if (forward.targetHost === data.targetHost &&
|
||||
forward.targetPort === data.targetPort) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
else if (forward.system) {
|
||||
return Promise.reject(new Error('Cannot rebind system port'))
|
||||
}
|
||||
else {
|
||||
removeForward(forward)
|
||||
}
|
||||
}
|
||||
|
||||
return adb.openTcp(options.serial, service.port)
|
||||
.timeout(10000)
|
||||
.then(function(conn) {
|
||||
var resolver = Promise.defer()
|
||||
|
||||
var forward = {
|
||||
devicePort: data.devicePort
|
||||
, targetHost: data.targetHost
|
||||
, targetPort: data.targetPort
|
||||
, system: !!data.system
|
||||
, privatePort: service.privatePorts.pop()
|
||||
, connection: conn
|
||||
}
|
||||
|
||||
var parser = conn.pipe(split())
|
||||
|
||||
parser.on('data', function(chunk) {
|
||||
var cmd = chunk.toString().trim()
|
||||
switch (cmd) {
|
||||
case 'OKAY':
|
||||
resolver.resolve(forward)
|
||||
break
|
||||
case 'FAIL':
|
||||
resolver.reject(new Error('Remote replied with FAIL'))
|
||||
break
|
||||
case 'CNCT':
|
||||
adb.openTcp(options.serial, forward.privatePort)
|
||||
.done(function(dstream) {
|
||||
return tryConnect(forward)
|
||||
.then(function(ustream) {
|
||||
ustream.pipe(dstream)
|
||||
dstream.pipe(ustream)
|
||||
})
|
||||
})
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
// Keep this around
|
||||
function endListener() {
|
||||
removeForward(forward)
|
||||
}
|
||||
|
||||
conn.on('end', endListener)
|
||||
|
||||
conn.write(util.format(
|
||||
'FRWD %d %d\n'
|
||||
, forward.devicePort
|
||||
, forward.privatePort
|
||||
))
|
||||
|
||||
return resolver.promise
|
||||
})
|
||||
}
|
||||
|
||||
function removeForward(data) {
|
||||
log.info('Removing reverse port forwarding on port %d', data.devicePort)
|
||||
var forward = service.forwards[data.devicePort]
|
||||
if (forward) {
|
||||
forward.connection.end()
|
||||
delete service.forwards[data.devicePort]
|
||||
}
|
||||
}
|
||||
|
||||
function tryConnect(data) {
|
||||
var resolver = Promise.defer()
|
||||
|
||||
var conn = net.connect({
|
||||
host: data.targetHost
|
||||
, port: data.targetPort
|
||||
})
|
||||
|
||||
function connectListener() {
|
||||
resolver.resolve(conn)
|
||||
}
|
||||
|
||||
function errorListener(err) {
|
||||
resolver.reject(err)
|
||||
}
|
||||
|
||||
conn.on('connect', connectListener)
|
||||
conn.on('error', errorListener)
|
||||
|
||||
return resolver.promise.finally(function() {
|
||||
conn.removeListener('connect', connectListener)
|
||||
conn.removeListener('error', errorListener)
|
||||
})
|
||||
}
|
||||
|
||||
function resetForwards() {
|
||||
Object.keys(service.forwards).forEach(function(privatePort) {
|
||||
service.forwards[privatePort].connection.end()
|
||||
delete service.forwards[privatePort]
|
||||
})
|
||||
}
|
||||
|
||||
function listForwards() {
|
||||
return Object.keys(service.forwards).map(function(privatePort) {
|
||||
var forward = service.forwards[privatePort]
|
||||
return {
|
||||
devicePort: forward.devicePort
|
||||
, targetHost: forward.targetHost
|
||||
, targetPort: forward.targetPort
|
||||
, system: !!forward.system
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return openService()
|
||||
.then(function() {
|
||||
router
|
||||
.on(wire.ForwardTestMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
tryConnect(message)
|
||||
.then(function(conn) {
|
||||
conn.end()
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
.catch(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('fail_connect')
|
||||
])
|
||||
})
|
||||
})
|
||||
.on(wire.ForwardCreateMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
createForward(message)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Reverse port forwarding failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('fail_forward')
|
||||
])
|
||||
})
|
||||
})
|
||||
.on(wire.ForwardRemoveMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
removeForward(message)
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
170
lib/units/device/plugins/group.js
Normal file
170
lib/units/device/plugins/group.js
Normal file
@@ -0,0 +1,170 @@
|
||||
var events = require('events')
|
||||
|
||||
var Promise = require('bluebird')
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
var grouputil = require('../../../util/grouputil')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./solo'))
|
||||
.dependency(require('./identity'))
|
||||
.dependency(require('./service'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../support/sub'))
|
||||
.dependency(require('../support/channels'))
|
||||
.define(function(options, solo, ident, service, router, push, sub, channels) {
|
||||
var log = logger.createLogger('device:plugins:group')
|
||||
, currentGroup = null
|
||||
, plugin = new events.EventEmitter()
|
||||
|
||||
plugin.get = Promise.method(function() {
|
||||
if (!currentGroup) {
|
||||
throw new grouputil.NoGroupError()
|
||||
}
|
||||
|
||||
return currentGroup
|
||||
})
|
||||
|
||||
plugin.join = function(newGroup, timeout) {
|
||||
return plugin.get()
|
||||
.then(function() {
|
||||
if (currentGroup.group !== newGroup.group) {
|
||||
throw new grouputil.AlreadyGroupedError()
|
||||
}
|
||||
|
||||
return currentGroup
|
||||
})
|
||||
.catch(grouputil.NoGroupError, function() {
|
||||
currentGroup = newGroup
|
||||
|
||||
log.important('Now owned by "%s"', currentGroup.email)
|
||||
log.info('Subscribing to group channel "%s"', currentGroup.group)
|
||||
|
||||
channels.register(currentGroup.group, {
|
||||
timeout: timeout || options.groupTimeout
|
||||
, alias: solo.channel
|
||||
})
|
||||
|
||||
sub.subscribe(currentGroup.group)
|
||||
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.JoinGroupMessage(
|
||||
options.serial
|
||||
, currentGroup
|
||||
))
|
||||
])
|
||||
|
||||
plugin.emit('join', currentGroup)
|
||||
|
||||
return currentGroup
|
||||
})
|
||||
}
|
||||
|
||||
plugin.keepalive = function() {
|
||||
if (currentGroup) {
|
||||
channels.keepalive(currentGroup.group)
|
||||
}
|
||||
}
|
||||
|
||||
plugin.leave = function(reason) {
|
||||
return plugin.get()
|
||||
.then(function(group) {
|
||||
log.important('No longer owned by "%s"', group.email)
|
||||
log.info('Unsubscribing from group channel "%s"', group.group)
|
||||
|
||||
channels.unregister(group.group)
|
||||
sub.unsubscribe(group.group)
|
||||
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.LeaveGroupMessage(
|
||||
options.serial
|
||||
, group
|
||||
, reason
|
||||
))
|
||||
])
|
||||
|
||||
currentGroup = null
|
||||
plugin.emit('leave', group)
|
||||
|
||||
return group
|
||||
})
|
||||
}
|
||||
|
||||
plugin.on('join', function() {
|
||||
service.acquireWakeLock()
|
||||
service.unlock()
|
||||
})
|
||||
|
||||
plugin.on('leave', function() {
|
||||
service.releaseWakeLock()
|
||||
service.lock()
|
||||
})
|
||||
|
||||
router
|
||||
.on(wire.GroupMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
grouputil.match(ident, message.requirements)
|
||||
.then(function() {
|
||||
return plugin.join(message.owner, message.timeout)
|
||||
})
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(grouputil.RequirementMismatchError, function(err) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
.catch(grouputil.AlreadyGroupedError, function(err) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
.on(wire.UngroupMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
grouputil.match(ident, message.requirements)
|
||||
.then(function() {
|
||||
return plugin.leave('ungroup_request')
|
||||
})
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(grouputil.NoGroupError, function(err) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
channels.on('timeout', function(channel) {
|
||||
if (currentGroup && channel === currentGroup.group) {
|
||||
plugin.leave('automatic_timeout')
|
||||
}
|
||||
})
|
||||
|
||||
lifecycle.observe(function() {
|
||||
return plugin.leave('device_absent')
|
||||
.catch(grouputil.NoGroupError, function() {
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
return plugin
|
||||
})
|
||||
143
lib/units/device/plugins/http.js
Normal file
143
lib/units/device/plugins/http.js
Normal file
@@ -0,0 +1,143 @@
|
||||
var util = require('util')
|
||||
var assert = require('assert')
|
||||
var http = require('http')
|
||||
|
||||
var Promise = require('bluebird')
|
||||
var syrup = require('syrup')
|
||||
var request = Promise.promisifyAll(require('request'))
|
||||
var httpProxy = require('http-proxy')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var devutil = require('../../../util/devutil')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
var streamutil = require('../../../util/streamutil')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../resources/remote'))
|
||||
.define(function(options, adb, remote) {
|
||||
var log = logger.createLogger('device:plugins:http')
|
||||
|
||||
var service = {
|
||||
port: 2870
|
||||
, privateUrl: null
|
||||
, publicUrl: null
|
||||
}
|
||||
|
||||
function openService() {
|
||||
log.info('Launching HTTP API')
|
||||
return devutil.ensureUnusedPort(adb, options.serial, service.port)
|
||||
.timeout(10000)
|
||||
.then(function() {
|
||||
return adb.shell(options.serial, [
|
||||
remote.bin
|
||||
, '--lib', remote.lib
|
||||
, '--listen-http', service.port
|
||||
])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
lifecycle.share('Remote shell', out)
|
||||
streamutil.talk(log, 'Remote shell says: "%s"', out)
|
||||
})
|
||||
.then(function() {
|
||||
return devutil.waitForPort(adb, options.serial, service.port)
|
||||
.timeout(20000)
|
||||
})
|
||||
.then(function(conn) {
|
||||
var ours = options.ports.pop()
|
||||
, everyones = options.ports.pop()
|
||||
, url = util.format('http://127.0.0.1:%d', ours)
|
||||
|
||||
// Don't need the connection
|
||||
conn.end()
|
||||
|
||||
log.info('Opening device HTTP API forwarder on "%s"', url)
|
||||
|
||||
service.privateUrl = url
|
||||
service.publicUrl = util.format(
|
||||
'http://%s:%s'
|
||||
, options.publicIp
|
||||
, everyones
|
||||
)
|
||||
|
||||
return adb.forward(
|
||||
options.serial
|
||||
, util.format('tcp:%d', ours)
|
||||
, util.format('tcp:%d', service.port)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function() {
|
||||
log.info(
|
||||
'Opening HTTP API proxy on "http://%s:%s"'
|
||||
, options.publicIp
|
||||
, everyones
|
||||
)
|
||||
|
||||
var resolver = Promise.defer()
|
||||
|
||||
function resolve() {
|
||||
lifecycle.share('Proxy server', proxyServer, {
|
||||
end: false
|
||||
})
|
||||
resolver.resolve()
|
||||
}
|
||||
|
||||
function reject(err) {
|
||||
resolver.reject(err)
|
||||
}
|
||||
|
||||
var proxy = httpProxy.createProxyServer({
|
||||
target: url
|
||||
, ws: false
|
||||
, xfwd: false
|
||||
})
|
||||
|
||||
var proxyServer = http.createServer(proxy.web)
|
||||
.listen(everyones)
|
||||
|
||||
proxyServer.on('listening', resolve)
|
||||
proxyServer.on('error', reject)
|
||||
|
||||
return resolver.promise.finally(function() {
|
||||
proxyServer.removeListener('listening', resolve)
|
||||
proxyServer.removeListener('error', reject)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return openService()
|
||||
.then(function() {
|
||||
return {
|
||||
getDisplay: function(id) {
|
||||
return request.getAsync({
|
||||
url: util.format(
|
||||
'%s/api/v1/displays/%d'
|
||||
, service.privateUrl
|
||||
, id
|
||||
)
|
||||
, json: true
|
||||
})
|
||||
.timeout(10000)
|
||||
.then(function(args) {
|
||||
var display = args[1]
|
||||
assert.ok('id' in display, 'Invalid response from HTTP API')
|
||||
// Fix rotation's old name
|
||||
if ('orientation' in display) {
|
||||
display.rotation = display.orientation
|
||||
delete display.orientation
|
||||
}
|
||||
return display
|
||||
})
|
||||
}
|
||||
, getDisplayUrl: function(id) {
|
||||
return util.format(
|
||||
'%s/api/v1/displays/%d/screenshot.jpg'
|
||||
, service.publicUrl
|
||||
, id
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
22
lib/units/device/plugins/identity.js
Normal file
22
lib/units/device/plugins/identity.js
Normal file
@@ -0,0 +1,22 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var devutil = require('../../../util/devutil')
|
||||
var logger = require('../../../util/logger')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/properties'))
|
||||
.dependency(require('./display'))
|
||||
.dependency(require('./phone'))
|
||||
.define(function(options, properties, display, phone) {
|
||||
var log = logger.createLogger('device:plugins:identity')
|
||||
|
||||
function solve() {
|
||||
log.info('Solving identity')
|
||||
var identity = devutil.makeIdentity(options.serial, properties)
|
||||
identity.display = display
|
||||
identity.phone = phone
|
||||
return identity
|
||||
}
|
||||
|
||||
return solve()
|
||||
})
|
||||
189
lib/units/device/plugins/install.js
Normal file
189
lib/units/device/plugins/install.js
Normal file
@@ -0,0 +1,189 @@
|
||||
var stream = require('stream')
|
||||
var url = require('url')
|
||||
var util = require('util')
|
||||
|
||||
var syrup = require('syrup')
|
||||
var request = require('request')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
var promiseutil = require('../../../util/promiseutil')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.define(function(options, adb, router, push) {
|
||||
var log = logger.createLogger('device:plugins:install')
|
||||
|
||||
router.on(wire.InstallMessage, function(channel, message) {
|
||||
var manifest = JSON.parse(message.manifest)
|
||||
, pkg = manifest.package
|
||||
|
||||
log.info('Installing package "%s" from "%s"', pkg, message.href)
|
||||
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
function sendProgress(data, progress) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.progress(data, progress)
|
||||
])
|
||||
}
|
||||
|
||||
function pushApp() {
|
||||
var req = request({
|
||||
url: url.resolve(options.storageUrl, message.href)
|
||||
})
|
||||
|
||||
// We need to catch the Content-Length on the fly or we risk
|
||||
// losing some of the initial chunks.
|
||||
var contentLength = null
|
||||
req.on('response', function(res) {
|
||||
contentLength = parseInt(res.headers['content-length'], 10)
|
||||
})
|
||||
|
||||
var source = new stream.Readable().wrap(req)
|
||||
var target = '/data/local/tmp/_app.apk'
|
||||
|
||||
return adb.push(options.serial, source, target)
|
||||
.timeout(10000)
|
||||
.then(function(transfer) {
|
||||
var resolver = Promise.defer()
|
||||
|
||||
function progressListener(stats) {
|
||||
if (contentLength) {
|
||||
// Progress 0% to 70%
|
||||
sendProgress(
|
||||
'pushing_app'
|
||||
, 50 * Math.max(0, Math.min(
|
||||
50
|
||||
, stats.bytesTransferred / contentLength
|
||||
))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function errorListener(err) {
|
||||
resolver.reject(err)
|
||||
}
|
||||
|
||||
function endListener() {
|
||||
resolver.resolve(target)
|
||||
}
|
||||
|
||||
transfer.on('progress', progressListener)
|
||||
transfer.on('error', errorListener)
|
||||
transfer.on('end', endListener)
|
||||
|
||||
return resolver.promise.finally(function() {
|
||||
transfer.removeListener('progress', progressListener)
|
||||
transfer.removeListener('error', errorListener)
|
||||
transfer.removeListener('end', endListener)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Progress 0%
|
||||
sendProgress('pushing_app', 0)
|
||||
pushApp()
|
||||
.then(function(apk) {
|
||||
var start = 50
|
||||
, end = 90
|
||||
, guesstimate = start
|
||||
|
||||
sendProgress('installing_app', guesstimate)
|
||||
return promiseutil.periodicNotify(
|
||||
adb.installRemote(options.serial, apk)
|
||||
.timeout(60000)
|
||||
.catch(function(err) {
|
||||
switch (err.code) {
|
||||
case 'INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES':
|
||||
log.info(
|
||||
'Uninstalling "%s" first due to inconsistent certificates'
|
||||
, pkg
|
||||
)
|
||||
return adb.uninstall(options.serial, pkg)
|
||||
.timeout(15000)
|
||||
.then(function() {
|
||||
return adb.installRemote(options.serial, apk)
|
||||
.timeout(60000)
|
||||
})
|
||||
default:
|
||||
return Promise.reject(err)
|
||||
}
|
||||
})
|
||||
, 250
|
||||
)
|
||||
.progressed(function() {
|
||||
guesstimate = Math.min(
|
||||
end
|
||||
, guesstimate + 1.5 * (end - guesstimate) / (end - start)
|
||||
)
|
||||
sendProgress('installing_app', guesstimate)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
if (message.launch) {
|
||||
if (manifest.application.launcherActivities.length) {
|
||||
var launchActivity = {
|
||||
action: 'android.intent.action.MAIN'
|
||||
, component: util.format(
|
||||
'%s/%s'
|
||||
, pkg
|
||||
, manifest.application.launcherActivities[0].name
|
||||
)
|
||||
, category: ['android.intent.category.LAUNCHER']
|
||||
, flags: 0x10200000
|
||||
}
|
||||
|
||||
log.info(
|
||||
'Launching activity with action "%s" on component "%s"'
|
||||
, launchActivity.action
|
||||
, launchActivity.component
|
||||
)
|
||||
// Progress 90%
|
||||
sendProgress('launching_app', 90)
|
||||
return adb.startActivity(options.serial, launchActivity)
|
||||
.timeout(15000)
|
||||
}
|
||||
}
|
||||
})
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Installation of package "%s" failed', pkg, err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('fail')
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.UninstallMessage, function(channel, message) {
|
||||
log.info('Uninstalling "%s"', message.packageName)
|
||||
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
adb.uninstall(options.serial, message.packageName)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Uninstallation failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('fail')
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
141
lib/units/device/plugins/logcat.js
Normal file
141
lib/units/device/plugins/logcat.js
Normal file
@@ -0,0 +1,141 @@
|
||||
var syrup = require('syrup')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('./group'))
|
||||
.define(function(options, adb, router, push, group) {
|
||||
var log = logger.createLogger('device:plugins:logcat')
|
||||
, plugin = Object.create(null)
|
||||
, activeLogcat = null
|
||||
|
||||
plugin.start = function(filters) {
|
||||
return group.get()
|
||||
.then(function(group) {
|
||||
return plugin.stop()
|
||||
.then(function() {
|
||||
log.info('Starting logcat')
|
||||
return adb.openLogcat(options.serial, {
|
||||
clear: true
|
||||
})
|
||||
})
|
||||
.timeout(10000)
|
||||
.then(function(logcat) {
|
||||
activeLogcat = logcat
|
||||
|
||||
function entryListener(entry) {
|
||||
push.send([
|
||||
group.group
|
||||
, wireutil.envelope(new wire.DeviceLogcatEntryMessage(
|
||||
options.serial
|
||||
, entry.date.getTime() / 1000
|
||||
, entry.pid
|
||||
, entry.tid
|
||||
, entry.priority
|
||||
, entry.tag
|
||||
, entry.message
|
||||
))
|
||||
])
|
||||
}
|
||||
|
||||
logcat.on('entry', entryListener)
|
||||
|
||||
return plugin.reset(filters)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
plugin.stop = Promise.method(function() {
|
||||
if (plugin.isRunning()) {
|
||||
log.info('Stopping logcat')
|
||||
activeLogcat.end()
|
||||
activeLogcat = null
|
||||
}
|
||||
})
|
||||
|
||||
plugin.reset = Promise.method(function(filters) {
|
||||
if (plugin.isRunning()) {
|
||||
activeLogcat
|
||||
.resetFilters()
|
||||
|
||||
if (filters.length) {
|
||||
activeLogcat.excludeAll()
|
||||
filters.forEach(function(filter) {
|
||||
activeLogcat.include(filter.tag, filter.priority)
|
||||
})
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error('Logcat is not running')
|
||||
}
|
||||
})
|
||||
|
||||
plugin.isRunning = function() {
|
||||
return !!activeLogcat
|
||||
}
|
||||
|
||||
lifecycle.observe(plugin.stop)
|
||||
group.on('leave', plugin.stop)
|
||||
|
||||
router
|
||||
.on(wire.LogcatStartMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.start(message.filters)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Unable to open logcat', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('fail')
|
||||
])
|
||||
})
|
||||
})
|
||||
.on(wire.LogcatApplyFiltersMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.reset(message.filters)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to apply logcat filters', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('fail')
|
||||
])
|
||||
})
|
||||
})
|
||||
.on(wire.LogcatStopMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.stop()
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Failed to stop logcat', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail('fail')
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
return plugin
|
||||
})
|
||||
27
lib/units/device/plugins/logger.js
Normal file
27
lib/units/device/plugins/logger.js
Normal file
@@ -0,0 +1,27 @@
|
||||
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/push'))
|
||||
.define(function(options, push) {
|
||||
// Forward all logs
|
||||
logger.on('entry', function(entry) {
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.DeviceLogMessage(
|
||||
options.serial
|
||||
, entry.timestamp / 1000
|
||||
, entry.priority
|
||||
, entry.tag
|
||||
, entry.pid
|
||||
, entry.message
|
||||
, entry.identifier
|
||||
))
|
||||
])
|
||||
})
|
||||
|
||||
return logger
|
||||
})
|
||||
21
lib/units/device/plugins/phone.js
Normal file
21
lib/units/device/plugins/phone.js
Normal file
@@ -0,0 +1,21 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./service'))
|
||||
.define(function(options, service) {
|
||||
var log = logger.createLogger('device:plugins:phone')
|
||||
|
||||
function fetch() {
|
||||
log.info('Fetching phone info')
|
||||
return service.getProperties([
|
||||
'imei'
|
||||
, 'phoneNumber'
|
||||
, 'iccid'
|
||||
, 'network'
|
||||
])
|
||||
}
|
||||
|
||||
return fetch()
|
||||
})
|
||||
35
lib/units/device/plugins/reboot.js
Normal file
35
lib/units/device/plugins/reboot.js
Normal file
@@ -0,0 +1,35 @@
|
||||
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/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.define(function(options, adb, router, push) {
|
||||
var log = logger.createLogger('device:plugins:reboot')
|
||||
|
||||
router.on(wire.RebootMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.important('Rebooting')
|
||||
|
||||
adb.reboot(options.serial)
|
||||
.timeout(30000)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.error(function(err) {
|
||||
log.error('Reboot failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
57
lib/units/device/plugins/ringer.js
Normal file
57
lib/units/device/plugins/ringer.js
Normal file
@@ -0,0 +1,57 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./service'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.define(function(options, service, router, push) {
|
||||
var log = logger.createLogger('device:plugins:ringer')
|
||||
|
||||
router.on(wire.RingerSetMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.info('Setting ringer mode to mode "%s"', message.mode)
|
||||
|
||||
service.setRingerMode(message.mode)
|
||||
.timeout(30000)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Setting ringer mode failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.RingerGetMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.info('Getting ringer mode')
|
||||
|
||||
service.getRingerMode()
|
||||
.timeout(30000)
|
||||
.then(function(mode) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success', mode)
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Getting ringer mode failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
66
lib/units/device/plugins/screenshot.js
Normal file
66
lib/units/device/plugins/screenshot.js
Normal file
@@ -0,0 +1,66 @@
|
||||
var http = require('http')
|
||||
var util = require('util')
|
||||
|
||||
var syrup = require('syrup')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
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('../support/storage'))
|
||||
.dependency(require('./display'))
|
||||
.define(function(options, router, push, storage, display) {
|
||||
var log = logger.createLogger('device:plugins:screenshot')
|
||||
var plugin = Object.create(null)
|
||||
|
||||
plugin.capture = function() {
|
||||
log.info('Capturing screenshot from %s', display.url)
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
var req = http.get(display.url)
|
||||
|
||||
function responseListener(res) {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(util.format(
|
||||
'Screenshot capture failed: HTTP %d'
|
||||
, res.statusCode
|
||||
)))
|
||||
}
|
||||
else {
|
||||
resolve(storage.store('image', res, {
|
||||
filename: util.format('%s.jpg', options.serial)
|
||||
, contentType: 'image/jpeg'
|
||||
, knownLength: +res.headers['content-length']
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
req.on('response', responseListener)
|
||||
req.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
router.on(wire.ScreenCaptureMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.capture()
|
||||
.then(function(file) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay('success', file)
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Screen capture failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
return plugin
|
||||
})
|
||||
33
lib/units/device/plugins/sd.js
Normal file
33
lib/units/device/plugins/sd.js
Normal file
@@ -0,0 +1,33 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./service'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.define(function(options, service, router, push) {
|
||||
var log = logger.createLogger('device:plugins:sd')
|
||||
|
||||
router.on(wire.SdStatusMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
log.info('Getting SD card status')
|
||||
service.getSdStatus(message)
|
||||
.timeout(30000)
|
||||
.then(function(mounted) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay(mounted ? 'sd_mounted' : 'sd_unmounted')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Getting SD card Status', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
695
lib/units/device/plugins/service.js
Normal file
695
lib/units/device/plugins/service.js
Normal file
@@ -0,0 +1,695 @@
|
||||
var util = require('util')
|
||||
var events = require('events')
|
||||
|
||||
var syrup = require('syrup')
|
||||
var Promise = require('bluebird')
|
||||
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
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')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
|
||||
function MessageResolver() {
|
||||
this.resolvers = Object.create(null)
|
||||
|
||||
this.await = function(id, resolver) {
|
||||
this.resolvers[id] = resolver
|
||||
return resolver.promise
|
||||
}
|
||||
|
||||
this.resolve = function(id, value) {
|
||||
var resolver = this.resolvers[id]
|
||||
delete this.resolvers[id]
|
||||
resolver.resolve(value)
|
||||
return resolver.promise
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../resources/service'))
|
||||
.define(function(options, adb, router, push, apk) {
|
||||
var log = logger.createLogger('device:plugins:service')
|
||||
var messageResolver = new MessageResolver()
|
||||
var plugin = new events.EventEmitter()
|
||||
|
||||
var agent = {
|
||||
socket: null
|
||||
, writer: null
|
||||
, port: 1090
|
||||
}
|
||||
|
||||
var service = {
|
||||
socket: null
|
||||
, writer: null
|
||||
, reader: null
|
||||
, port: 1100
|
||||
}
|
||||
|
||||
function openAgent() {
|
||||
log.info('Launching agent')
|
||||
return stopAgent()
|
||||
.timeout(15000)
|
||||
.then(function() {
|
||||
return devutil.ensureUnusedPort(adb, options.serial, agent.port)
|
||||
.timeout(10000)
|
||||
})
|
||||
.then(function() {
|
||||
return adb.shell(options.serial, util.format(
|
||||
"export CLASSPATH='%s'; exec app_process /system/bin '%s'"
|
||||
, apk.path
|
||||
, apk.main
|
||||
))
|
||||
.timeout(10000)
|
||||
})
|
||||
.then(function(out) {
|
||||
lifecycle.share('Agent shell', out)
|
||||
streamutil.talk(log, 'Agent says: "%s"', out)
|
||||
})
|
||||
.then(function() {
|
||||
return devutil.waitForPort(adb, options.serial, agent.port)
|
||||
.timeout(10000)
|
||||
})
|
||||
.then(function(conn) {
|
||||
agent.socket = conn
|
||||
agent.writer = new ms.DelimitingStream()
|
||||
agent.writer.pipe(conn)
|
||||
lifecycle.share('Agent connection', conn)
|
||||
})
|
||||
}
|
||||
|
||||
function stopAgent() {
|
||||
return devutil.killProcsByComm(
|
||||
adb
|
||||
, options.serial
|
||||
, 'stf.agent'
|
||||
, 'stf.agent'
|
||||
)
|
||||
}
|
||||
|
||||
function callService(intent) {
|
||||
return adb.shell(options.serial, util.format(
|
||||
'am startservice --user 0 %s'
|
||||
, intent
|
||||
))
|
||||
.timeout(15000)
|
||||
.then(function(out) {
|
||||
return streamutil.findLine(out, /^Error/)
|
||||
.finally(function() {
|
||||
out.end()
|
||||
})
|
||||
.timeout(10000)
|
||||
.then(function(line) {
|
||||
if (line.indexOf('--user') !== -1) {
|
||||
return adb.shell(options.serial, util.format(
|
||||
'am startservice %s'
|
||||
, intent
|
||||
))
|
||||
.timeout(15000)
|
||||
.then(function() {
|
||||
return streamutil.findLine(out, /^Error/)
|
||||
.finally(function() {
|
||||
out.end()
|
||||
})
|
||||
.timeout(10000)
|
||||
.then(function(line) {
|
||||
throw new Error(util.format(
|
||||
'Service had an error: "%s"'
|
||||
, line
|
||||
))
|
||||
})
|
||||
.catch(streamutil.NoSuchLineError, function() {
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
else {
|
||||
throw new Error(util.format(
|
||||
'Service had an error: "%s"'
|
||||
, line
|
||||
))
|
||||
}
|
||||
})
|
||||
.catch(streamutil.NoSuchLineError, function() {
|
||||
return true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// The APK should be up to date at this point. If it was reinstalled, the
|
||||
// service should have been automatically stopped while it was happening.
|
||||
// So, we should be good to go.
|
||||
function openService() {
|
||||
log.info('Launching service')
|
||||
return callService(util.format(
|
||||
"-a '%s' -n '%s'"
|
||||
, apk.startIntent.action
|
||||
, apk.startIntent.component
|
||||
))
|
||||
.then(function() {
|
||||
return devutil.waitForPort(adb, options.serial, service.port)
|
||||
.timeout(15000)
|
||||
})
|
||||
.then(function(conn) {
|
||||
service.socket = conn
|
||||
service.reader = conn.pipe(new ms.DelimitedStream())
|
||||
service.reader.on('data', handleEnvelope)
|
||||
service.writer = new ms.DelimitingStream()
|
||||
service.writer.pipe(conn)
|
||||
lifecycle.share('Service connection', conn)
|
||||
})
|
||||
}
|
||||
|
||||
function handleEnvelope(data) {
|
||||
var envelope = apk.wire.Envelope.decode(data)
|
||||
, message
|
||||
if (envelope.id !== null) {
|
||||
messageResolver.resolve(envelope.id, envelope.message)
|
||||
}
|
||||
else {
|
||||
switch (envelope.type) {
|
||||
case apk.wire.MessageType.EVENT_AIRPLANE_MODE:
|
||||
message = apk.wire.AirplaneModeEvent.decode(envelope.message)
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.AirplaneModeEvent(
|
||||
options.serial
|
||||
, message.enabled
|
||||
))
|
||||
])
|
||||
plugin.emit('airplaneModeChange', message)
|
||||
break
|
||||
case apk.wire.MessageType.EVENT_BATTERY:
|
||||
message = apk.wire.BatteryEvent.decode(envelope.message)
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.BatteryEvent(
|
||||
options.serial
|
||||
, message.status
|
||||
, message.health
|
||||
, message.source
|
||||
, message.level
|
||||
, message.scale
|
||||
, message.temp
|
||||
, message.voltage
|
||||
))
|
||||
])
|
||||
plugin.emit('batteryChange', message)
|
||||
break
|
||||
case apk.wire.MessageType.EVENT_BROWSER_PACKAGE:
|
||||
message = apk.wire.BrowserPackageEvent.decode(envelope.message)
|
||||
plugin.emit('browserPackageChange', message)
|
||||
break
|
||||
case apk.wire.MessageType.EVENT_CONNECTIVITY:
|
||||
message = apk.wire.ConnectivityEvent.decode(envelope.message)
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.ConnectivityEvent(
|
||||
options.serial
|
||||
, message.connected
|
||||
, message.type
|
||||
, message.subtype
|
||||
, message.failover
|
||||
, message.roaming
|
||||
))
|
||||
])
|
||||
plugin.emit('connectivityChange', message)
|
||||
break
|
||||
case apk.wire.MessageType.EVENT_PHONE_STATE:
|
||||
message = apk.wire.PhoneStateEvent.decode(envelope.message)
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.PhoneStateEvent(
|
||||
options.serial
|
||||
, message.state
|
||||
, message.manual
|
||||
, message.operator
|
||||
))
|
||||
])
|
||||
plugin.emit('phoneStateChange', message)
|
||||
break
|
||||
case apk.wire.MessageType.EVENT_ROTATION:
|
||||
message = apk.wire.RotationEvent.decode(envelope.message)
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.RotationEvent(
|
||||
options.serial
|
||||
, message.rotation
|
||||
))
|
||||
])
|
||||
plugin.emit('rotationChange', message)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keyEvent(data) {
|
||||
return runAgentCommand(
|
||||
apk.wire.MessageType.DO_KEYEVENT
|
||||
, new apk.wire.KeyEventRequest(data)
|
||||
)
|
||||
}
|
||||
|
||||
plugin.type = function(text) {
|
||||
return runAgentCommand(
|
||||
apk.wire.MessageType.DO_TYPE
|
||||
, new apk.wire.DoTypeRequest(text)
|
||||
)
|
||||
}
|
||||
|
||||
plugin.paste = function(text) {
|
||||
return plugin.setClipboard(text)
|
||||
.then(function() {
|
||||
keyEvent({
|
||||
event: apk.wire.KeyEvent.PRESS
|
||||
, keyCode: adb.Keycode.KEYCODE_V
|
||||
, ctrlKey: true
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
plugin.copy = function() {
|
||||
// @TODO Not sure how to force the device to copy the current selection
|
||||
// yet.
|
||||
return plugin.getClipboard()
|
||||
}
|
||||
|
||||
plugin.getDisplay = function(id) {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_DISPLAY
|
||||
, new apk.wire.GetDisplayRequest(id)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetDisplayResponse.decode(data)
|
||||
if (response.success) {
|
||||
return {
|
||||
id: id
|
||||
, width: response.width
|
||||
, height: response.height
|
||||
, xdpi: response.xdpi
|
||||
, ydpi: response.ydpi
|
||||
, fps: response.fps
|
||||
, density: response.density
|
||||
, rotation: response.rotation
|
||||
, secure: response.secure
|
||||
}
|
||||
}
|
||||
throw new Error('Unable to retrieve display information')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.wake = function() {
|
||||
return runAgentCommand(
|
||||
apk.wire.MessageType.DO_WAKE
|
||||
, new apk.wire.DoWakeRequest()
|
||||
)
|
||||
}
|
||||
|
||||
plugin.rotate = function(rotation) {
|
||||
return runAgentCommand(
|
||||
apk.wire.MessageType.SET_ROTATION
|
||||
, new apk.wire.SetRotationRequest(rotation, false)
|
||||
)
|
||||
}
|
||||
|
||||
plugin.freezeRotation = function(rotation) {
|
||||
return runAgentCommand(
|
||||
apk.wire.MessageType.SET_ROTATION
|
||||
, new apk.wire.SetRotationRequest(rotation, true)
|
||||
)
|
||||
}
|
||||
|
||||
plugin.thawRotation = function() {
|
||||
return runAgentCommand(
|
||||
apk.wire.MessageType.SET_ROTATION
|
||||
, new apk.wire.SetRotationRequest(0, false)
|
||||
)
|
||||
}
|
||||
|
||||
plugin.version = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_VERSION
|
||||
, new apk.wire.GetVersionRequest()
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetVersionResponse.decode(data)
|
||||
if (response.success) {
|
||||
return response.version
|
||||
}
|
||||
throw new Error('Unable to retrieve version')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.unlock = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.SET_KEYGUARD_STATE
|
||||
, new apk.wire.SetKeyguardStateRequest(false)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.SetKeyguardStateResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to unlock device')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.lock = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.SET_KEYGUARD_STATE
|
||||
, new apk.wire.SetKeyguardStateRequest(true)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.SetKeyguardStateResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to lock device')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.acquireWakeLock = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.SET_WAKE_LOCK
|
||||
, new apk.wire.SetWakeLockRequest(true)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.SetWakeLockResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to acquire WakeLock')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.releaseWakeLock = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.SET_WAKE_LOCK
|
||||
, new apk.wire.SetWakeLockRequest(false)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.SetWakeLockResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to release WakeLock')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.identity = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.DO_IDENTIFY
|
||||
, new apk.wire.DoIdentifyRequest(options.serial)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.DoIdentifyResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to identify device')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.setClipboard = function(text) {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.SET_CLIPBOARD
|
||||
, new apk.wire.SetClipboardRequest(
|
||||
apk.wire.ClipboardType.TEXT
|
||||
, text
|
||||
)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.SetClipboardResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to set clipboard')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.getClipboard = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_CLIPBOARD
|
||||
, new apk.wire.GetClipboardRequest(
|
||||
apk.wire.ClipboardType.TEXT
|
||||
)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetClipboardResponse.decode(data)
|
||||
if (response.success) {
|
||||
switch (response.type) {
|
||||
case apk.wire.ClipboardType.TEXT:
|
||||
return response.text
|
||||
}
|
||||
}
|
||||
throw new Error('Unable to get clipboard')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.getBrowsers = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_BROWSERS
|
||||
, new apk.wire.GetBrowsersRequest()
|
||||
)
|
||||
.timeout(15000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetBrowsersResponse.decode(data)
|
||||
if (response.success) {
|
||||
delete response.success
|
||||
return response
|
||||
}
|
||||
throw new Error('Unable to get browser list')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.getProperties = function(properties) {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_PROPERTIES
|
||||
, new apk.wire.GetPropertiesRequest(properties)
|
||||
)
|
||||
.timeout(15000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetPropertiesResponse.decode(data)
|
||||
if (response.success) {
|
||||
var mapped = Object.create(null)
|
||||
response.properties.forEach(function(property) {
|
||||
mapped[property.name] = property.value
|
||||
})
|
||||
return mapped
|
||||
}
|
||||
throw new Error('Unable to get properties')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.getAccounts = function(data) {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_ACCOUNTS
|
||||
, new apk.wire.GetAccountsRequest({type: data.type})
|
||||
)
|
||||
.timeout(15000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetAccountsResponse.decode(data)
|
||||
if (response.success) {
|
||||
return response.accounts
|
||||
}
|
||||
throw new Error('No accounts returned')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.removeAccount = function(data) {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.DO_REMOVE_ACCOUNT
|
||||
, new apk.wire.DoRemoveAccountRequest({
|
||||
type: data.type
|
||||
, account: data.account
|
||||
})
|
||||
)
|
||||
.timeout(15000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.DoRemoveAccountResponse.decode(data)
|
||||
if (response.success) {
|
||||
return true
|
||||
}
|
||||
throw new Error('Unable to remove account')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.addAccountMenu = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.DO_ADD_ACCOUNT_MENU
|
||||
, new apk.wire.DoAddAccountMenuRequest()
|
||||
)
|
||||
.timeout(15000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.DoAddAccountMenuResponse.decode(data)
|
||||
if (response.success) {
|
||||
return true
|
||||
}
|
||||
throw new Error('Unable to show add account menu')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.setRingerMode = function(mode) {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.SET_RINGER_MODE
|
||||
, new apk.wire.SetRingerModeRequest(mode)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.SetRingerModeResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to set ringer mode')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.getRingerMode = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_RINGER_MODE
|
||||
, new apk.wire.GetRingerModeRequest()
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetRingerModeResponse.decode(data)
|
||||
// Reflection to decode enums to their string values, otherwise
|
||||
// we only get an integer
|
||||
var ringerMode = apk.builder.lookup('jp.co.cyberagent.stf.proto.RingerMode')
|
||||
.children[response.mode].name
|
||||
if (response.success) {
|
||||
return ringerMode
|
||||
}
|
||||
throw new Error('Unable to get ringer mode')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.setWifiEnabled = function(enabled) {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.SET_WIFI_ENABLED
|
||||
, new apk.wire.SetWifiEnabledRequest(enabled)
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.SetWifiEnabledResponse.decode(data)
|
||||
if (!response.success) {
|
||||
throw new Error('Unable to set Wifi')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
plugin.getWifiStatus = function() {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_WIFI_STATUS
|
||||
, new apk.wire.GetWifiStatusRequest()
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetWifiStatusResponse.decode(data)
|
||||
if (response.success) {
|
||||
return response.status
|
||||
}
|
||||
throw new Error('Unable to get Wifi status')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.getSdStatus = function () {
|
||||
return runServiceCommand(
|
||||
apk.wire.MessageType.GET_SD_STATUS
|
||||
, new apk.wire.GetSdStatusRequest()
|
||||
)
|
||||
.timeout(10000)
|
||||
.then(function(data) {
|
||||
var response = apk.wire.GetSdStatusResponse.decode(data)
|
||||
if (response.success) {
|
||||
return response.mounted
|
||||
}
|
||||
throw new Error('Unable to get SD card status')
|
||||
})
|
||||
}
|
||||
|
||||
plugin.pressKey = function(key) {
|
||||
keyEvent({event: apk.wire.KeyEvent.PRESS, keyCode: keyutil.namedKey(key)})
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
function runServiceCommand(type, cmd) {
|
||||
var resolver = Promise.defer()
|
||||
var id = Math.floor(Math.random() * 0xFFFFFF)
|
||||
service.writer.write(new apk.wire.Envelope(
|
||||
id
|
||||
, type
|
||||
, cmd.encodeNB()
|
||||
).encodeNB())
|
||||
return messageResolver.await(id, resolver)
|
||||
}
|
||||
|
||||
function runAgentCommand(type, cmd) {
|
||||
agent.writer.write(new apk.wire.Envelope(
|
||||
null
|
||||
, type
|
||||
, cmd.encodeNB()
|
||||
).encodeNB())
|
||||
}
|
||||
|
||||
return openAgent()
|
||||
.then(openService)
|
||||
.then(function() {
|
||||
router
|
||||
.on(wire.PhysicalIdentifyMessage, function(channel) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
plugin.identity()
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.on(wire.KeyDownMessage, function(channel, message) {
|
||||
try {
|
||||
keyEvent({
|
||||
event: apk.wire.KeyEvent.DOWN
|
||||
, keyCode: keyutil.namedKey(message.key)
|
||||
})
|
||||
}
|
||||
catch(e) {
|
||||
log.warn(e.message)
|
||||
}
|
||||
})
|
||||
.on(wire.KeyUpMessage, function(channel, message) {
|
||||
try {
|
||||
keyEvent({
|
||||
event: apk.wire.KeyEvent.UP
|
||||
, keyCode: keyutil.namedKey(message.key)
|
||||
})
|
||||
}
|
||||
catch(e) {
|
||||
log.warn(e.message)
|
||||
}
|
||||
})
|
||||
.on(wire.KeyPressMessage, function(channel, message) {
|
||||
try {
|
||||
keyEvent({
|
||||
event: apk.wire.KeyEvent.PRESS
|
||||
, keyCode: keyutil.namedKey(message.key)
|
||||
})
|
||||
}
|
||||
catch(e) {
|
||||
log.warn(e.message)
|
||||
}
|
||||
})
|
||||
.on(wire.TypeMessage, function(channel, message) {
|
||||
plugin.type(message.text)
|
||||
})
|
||||
.on(wire.RotateMessage, function(channel, message) {
|
||||
plugin.rotate(message.rotation)
|
||||
})
|
||||
})
|
||||
.return(plugin)
|
||||
})
|
||||
86
lib/units/device/plugins/shell.js
Normal file
86
lib/units/device/plugins/shell.js
Normal file
@@ -0,0 +1,86 @@
|
||||
var Promise = require('bluebird')
|
||||
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/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../support/sub'))
|
||||
.define(function(options, adb, router, push, sub) {
|
||||
var log = logger.createLogger('device:plugins:shell')
|
||||
|
||||
router.on(wire.ShellCommandMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
|
||||
log.info('Running shell command "%s"', message.command)
|
||||
|
||||
adb.shell(options.serial, message.command)
|
||||
.timeout(10000)
|
||||
.then(function(stream) {
|
||||
var resolver = Promise.defer()
|
||||
, timer
|
||||
|
||||
function keepAliveListener(channel, message) {
|
||||
clearTimeout(timer)
|
||||
timer = setTimeout(forceStop, message.timeout)
|
||||
}
|
||||
|
||||
function readableListener() {
|
||||
var chunk
|
||||
while ((chunk = stream.read())) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.progress(chunk)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
function endListener() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay(null)
|
||||
])
|
||||
resolver.resolve()
|
||||
}
|
||||
|
||||
function errorListener(err) {
|
||||
resolver.reject(err)
|
||||
}
|
||||
|
||||
function forceStop() {
|
||||
stream.end()
|
||||
}
|
||||
|
||||
stream.setEncoding('utf8')
|
||||
|
||||
stream.on('readable', readableListener)
|
||||
stream.on('end', endListener)
|
||||
stream.on('error', errorListener)
|
||||
|
||||
sub.subscribe(channel)
|
||||
router.on(wire.ShellKeepAliveMessage, keepAliveListener)
|
||||
|
||||
timer = setTimeout(forceStop, message.timeout)
|
||||
|
||||
return resolver.promise.finally(function() {
|
||||
stream.removeListener('readable', readableListener)
|
||||
stream.removeListener('end', endListener)
|
||||
stream.removeListener('error', errorListener)
|
||||
sub.unsubscribe(channel)
|
||||
router.removeListener(wire.ShellKeepAliveMessage, keepAliveListener)
|
||||
clearTimeout(timer)
|
||||
})
|
||||
})
|
||||
.error(function(err) {
|
||||
log.error('Shell command "%s" failed', message.command, err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
50
lib/units/device/plugins/solo.js
Normal file
50
lib/units/device/plugins/solo.js
Normal file
@@ -0,0 +1,50 @@
|
||||
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/sub'))
|
||||
.dependency(require('../support/push'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('./identity'))
|
||||
.define(function(options, sub, push, router, identity) {
|
||||
var log = logger.createLogger('device:plugins:solo')
|
||||
var channel = wireutil.makePrivateChannel()
|
||||
|
||||
log.info('Subscribing to permanent channel "%s"', channel)
|
||||
sub.subscribe(channel)
|
||||
|
||||
router.on(wire.ProbeMessage, function() {
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.DeviceIdentityMessage(
|
||||
options.serial
|
||||
, identity.platform
|
||||
, identity.manufacturer
|
||||
, identity.operator
|
||||
, identity.model
|
||||
, identity.version
|
||||
, identity.abi
|
||||
, identity.sdk
|
||||
, new wire.DeviceDisplayMessage(identity.display)
|
||||
, new wire.DevicePhoneMessage(identity.phone)
|
||||
, identity.product
|
||||
))
|
||||
])
|
||||
})
|
||||
|
||||
return {
|
||||
channel: channel
|
||||
, poke: function() {
|
||||
push.send([
|
||||
wireutil.global
|
||||
, wireutil.envelope(new wire.DevicePokeMessage(
|
||||
options.serial
|
||||
, channel
|
||||
))
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
46
lib/units/device/plugins/stats.js
Normal file
46
lib/units/device/plugins/stats.js
Normal file
@@ -0,0 +1,46 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var devutil = require('../../../util/devutil')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
var streamutil = require('../../../util/streamutil')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../resources/remote'))
|
||||
.define(function(options, adb, remote) {
|
||||
var log = logger.createLogger('device:plugins:stats')
|
||||
|
||||
var service = {
|
||||
port: 2830
|
||||
}
|
||||
|
||||
function openService() {
|
||||
return devutil.ensureUnusedPort(adb, options.serial, service.port)
|
||||
.timeout(10000)
|
||||
.then(function() {
|
||||
return adb.shell(options.serial, [
|
||||
remote.bin
|
||||
, '--lib', remote.lib
|
||||
, '--listen-stats', service.port
|
||||
])
|
||||
.timeout(10000)
|
||||
.then(function(out) {
|
||||
lifecycle.share('Stats shell', out)
|
||||
streamutil.talk(log, 'Stats shell says: "%s"', out)
|
||||
})
|
||||
})
|
||||
.then(function() {
|
||||
return devutil.waitForPort(adb, options.serial, service.port)
|
||||
.timeout(15000)
|
||||
})
|
||||
.then(function(conn) {
|
||||
return lifecycle.share('Stats connection', conn)
|
||||
})
|
||||
}
|
||||
|
||||
return openService()
|
||||
.then(function() {
|
||||
return {}
|
||||
})
|
||||
})
|
||||
40
lib/units/device/plugins/store.js
Normal file
40
lib/units/device/plugins/store.js
Normal file
@@ -0,0 +1,40 @@
|
||||
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('../support/adb'))
|
||||
.define(function(options, router, push, adb) {
|
||||
var log = logger.createLogger('device:plugins:store')
|
||||
|
||||
router.on(wire.StoreOpenMessage, function(channel) {
|
||||
log.info('Opening Play Store')
|
||||
|
||||
var reply = wireutil.reply(options.serial)
|
||||
adb.startActivity(options.serial, {
|
||||
action: 'android.intent.action.MAIN'
|
||||
, component: 'com.android.vending/.AssetBrowserActivity'
|
||||
// FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
|
||||
// FLAG_ACTIVITY_BROUGHT_TO_FRONT
|
||||
// FLAG_ACTIVITY_NEW_TASK
|
||||
, flags: 0x10600000
|
||||
})
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Play Store could not be opened', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail()
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
142
lib/units/device/plugins/touch.js
Normal file
142
lib/units/device/plugins/touch.js
Normal file
@@ -0,0 +1,142 @@
|
||||
var Promise = require('bluebird')
|
||||
var syrup = require('syrup')
|
||||
var monkey = require('adbkit-monkey')
|
||||
|
||||
var wire = require('../../../wire')
|
||||
var devutil = require('../../../util/devutil')
|
||||
var logger = require('../../../util/logger')
|
||||
var lifecycle = require('../../../util/lifecycle')
|
||||
var streamutil = require('../../../util/streamutil')
|
||||
var SeqQueue = require('../../../wire/seqqueue')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('../support/adb'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../resources/remote'))
|
||||
.dependency(require('./display'))
|
||||
.dependency(require('./data'))
|
||||
.define(function(options, adb, router, remote, display, data) {
|
||||
var log = logger.createLogger('device:plugins:touch')
|
||||
var plugin = Object.create(null)
|
||||
|
||||
var service = {
|
||||
port: 2820
|
||||
}
|
||||
|
||||
function openService() {
|
||||
log.info('Launching touch service')
|
||||
return devutil.ensureUnusedPort(adb, options.serial, service.port)
|
||||
.timeout(10000)
|
||||
.then(function() {
|
||||
return adb.shell(options.serial, [
|
||||
remote.bin
|
||||
, '--lib', remote.lib
|
||||
, '--listen-input', service.port
|
||||
])
|
||||
.timeout(10000)
|
||||
})
|
||||
.then(function(out) {
|
||||
lifecycle.share('Touch shell', out)
|
||||
streamutil.talk(log, 'Touch shell says: "%s"', out)
|
||||
})
|
||||
.then(function() {
|
||||
return devutil.waitForPort(adb, options.serial, service.port)
|
||||
.timeout(15000)
|
||||
})
|
||||
.then(function(conn) {
|
||||
return Promise.promisifyAll(monkey.connectStream(conn))
|
||||
})
|
||||
.then(function(monkey) {
|
||||
return lifecycle.share('Touch monkey', monkey)
|
||||
})
|
||||
}
|
||||
|
||||
function modifyCoords(message) {
|
||||
message.x = Math.floor(message.x * display.width)
|
||||
message.y = Math.floor(message.y * display.height)
|
||||
}
|
||||
|
||||
return openService()
|
||||
.then(function(monkey) {
|
||||
var queue = new SeqQueue()
|
||||
, pressure = (data && data.touch && data.touch.defaultPressure) || 50
|
||||
|
||||
log.info('Setting default pressure to %d', pressure)
|
||||
|
||||
plugin.touchDown = function(point) {
|
||||
modifyCoords(point)
|
||||
monkey.sendAsync([
|
||||
'touch down'
|
||||
, point.x
|
||||
, point.y
|
||||
, pressure
|
||||
].join(' '))
|
||||
.catch(function(err) {
|
||||
log.error('touchDown failed', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
plugin.touchMove = function(point) {
|
||||
modifyCoords(point)
|
||||
monkey.sendAsync([
|
||||
'touch move'
|
||||
, point.x
|
||||
, point.y
|
||||
, pressure
|
||||
].join(' '))
|
||||
.catch(function(err) {
|
||||
log.error('touchMove failed', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
plugin.touchUp = function(point) {
|
||||
modifyCoords(point)
|
||||
monkey.sendAsync([
|
||||
'touch up'
|
||||
, point.x
|
||||
, point.y
|
||||
, pressure
|
||||
].join(' '))
|
||||
.catch(function(err) {
|
||||
log.error('touchUp failed', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
plugin.tap = function(point) {
|
||||
modifyCoords(point)
|
||||
monkey.sendAsync([
|
||||
'tap'
|
||||
, point.x
|
||||
, point.y
|
||||
, pressure
|
||||
].join(' '))
|
||||
.catch(function(err) {
|
||||
log.error('tap failed', err.stack)
|
||||
})
|
||||
}
|
||||
|
||||
router
|
||||
.on(wire.TouchDownMessage, function(channel, message) {
|
||||
queue.push(message.seq, function() {
|
||||
plugin.touchDown(message)
|
||||
})
|
||||
})
|
||||
.on(wire.TouchMoveMessage, function(channel, message) {
|
||||
queue.push(message.seq, function() {
|
||||
plugin.touchMove(message)
|
||||
})
|
||||
})
|
||||
.on(wire.TouchUpMessage, function(channel, message) {
|
||||
queue.push(message.seq, function() {
|
||||
plugin.touchUp(message)
|
||||
})
|
||||
|
||||
// Reset queue
|
||||
queue = new SeqQueue()
|
||||
})
|
||||
.on(wire.TapMessage, function(channel, message) {
|
||||
plugin.tap(message)
|
||||
})
|
||||
})
|
||||
.return(plugin)
|
||||
})
|
||||
53
lib/units/device/plugins/wifi.js
Normal file
53
lib/units/device/plugins/wifi.js
Normal file
@@ -0,0 +1,53 @@
|
||||
var syrup = require('syrup')
|
||||
|
||||
var logger = require('../../../util/logger')
|
||||
var wire = require('../../../wire')
|
||||
var wireutil = require('../../../wire/util')
|
||||
|
||||
module.exports = syrup.serial()
|
||||
.dependency(require('./service'))
|
||||
.dependency(require('../support/router'))
|
||||
.dependency(require('../support/push'))
|
||||
.define(function(options, service, router, push) {
|
||||
var log = logger.createLogger('device:plugins:wifi')
|
||||
|
||||
router.on(wire.WifiSetEnabledMessage, function(channel, message) {
|
||||
var reply = wireutil.reply(options.serial)
|
||||
log.info('Setting Wifi "%s"', message.enabled)
|
||||
service.setWifiEnabled(message.enabled)
|
||||
.timeout(30000)
|
||||
.then(function() {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay()
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Setting Wifi enabled failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
router.on(wire.WifiGetStatusMessage, function(channel){
|
||||
var reply = wireutil.reply(options.serial)
|
||||
log.info('Getting Wifi status')
|
||||
service.getWifiStatus()
|
||||
.timeout(30000)
|
||||
.then(function(enabled) {
|
||||
push.send([
|
||||
channel
|
||||
, reply.okay(enabled ? 'wifi_enabled' : 'wifi_disabled')
|
||||
])
|
||||
})
|
||||
.catch(function(err) {
|
||||
log.error('Getting Wifi status failed', err.stack)
|
||||
push.send([
|
||||
channel
|
||||
, reply.fail(err.message)
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user