device_api-android 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/51-android.rules +60 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +24 -0
- data/LICENSE +21 -0
- data/README.md +118 -0
- data/device_api-android.gemspec +14 -0
- data/lib/device_api/android.rb +30 -0
- data/lib/device_api/android/aapt.rb +50 -0
- data/lib/device_api/android/adb.rb +217 -0
- data/lib/device_api/android/device.rb +178 -0
- data/lib/device_api/android/devices/default.rb +0 -0
- data/lib/device_api/android/signing.rb +80 -0
- data/spec/adb_spec.rb +138 -0
- data/spec/android_device_spec.rb +49 -0
- data/spec/android_spec.rb +207 -0
- data/spec/helpers/stub_helper_spec.rb +4 -0
- metadata +98 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d7f064ddcac962170d4089858a6a791625388805
|
4
|
+
data.tar.gz: 819b3797ef5b61c59383682c3758dcfa48ceb00d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 819d33204b007a4ddfad07262978c0832f980287810d170e23f0034f7b8e06a203699bf98da9538a71d6549121a8de2e5010d29367120e3820ba090a0a923aef
|
7
|
+
data.tar.gz: b55962d0d3e0511ea0877f4908c7d552916009b8d8ea43101ed16d90a2e656b58c5b67795766a2b4563fc502a850471408f9257ae42e2f8bfe52b7788edcc108
|
data/.gitignore
ADDED
data/51-android.rules
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
# adb protocol on passion (Nexus One)
|
2
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e12", MODE="0600", OWNER="hive"
|
3
|
+
# fastboot protocol on passion (Nexus One)
|
4
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0bb4", ATTR{idProduct}=="0fff", MODE="0600", OWNER="hive"
|
5
|
+
# adb protocol on crespo/crespo4g (Nexus S)
|
6
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e22", MODE="0600", OWNER="hive"
|
7
|
+
# fastboot protocol on crespo/crespo4g (Nexus S)
|
8
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e20", MODE="0600", OWNER="hive"
|
9
|
+
# adb protocol on stingray/wingray (Xoom)
|
10
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="22b8", ATTR{idProduct}=="70a9", MODE="0600", OWNER="hive"
|
11
|
+
# fastboot protocol on stingray/wingray (Xoom)
|
12
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="708c", MODE="0600", OWNER="hive"
|
13
|
+
# adb protocol on maguro/toro (Galaxy Nexus)
|
14
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", ATTR{idProduct}=="6860", MODE="0600", OWNER="hive"
|
15
|
+
# fastboot protocol on maguro/toro (Galaxy Nexus)
|
16
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e30", MODE="0600", OWNER="hive"
|
17
|
+
# adb protocol on panda (PandaBoard)
|
18
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0451", ATTR{idProduct}=="d101", MODE="0600", OWNER="hive"
|
19
|
+
# adb protocol on panda (PandaBoard ES)
|
20
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="d002", MODE="0600", OWNER="hive"
|
21
|
+
# fastboot protocol on panda (PandaBoard)
|
22
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0451", ATTR{idProduct}=="d022", MODE="0600", OWNER="hive"
|
23
|
+
# usbboot protocol on panda (PandaBoard)
|
24
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0451", ATTR{idProduct}=="d00f", MODE="0600", OWNER="hive"
|
25
|
+
# usbboot protocol on panda (PandaBoard ES)
|
26
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0451", ATTR{idProduct}=="d010", MODE="0600", OWNER="hive"
|
27
|
+
# adb protocol on grouper/tilapia (Nexus 7)
|
28
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e42", MODE="0600", OWNER="hive"
|
29
|
+
# fastboot protocol on grouper/tilapia (Nexus 7)
|
30
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e40", MODE="0600", OWNER="hive"
|
31
|
+
# adb protocol on manta (Nexus 10)
|
32
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4ee2", MODE="0600", OWNER="hive"
|
33
|
+
# fastboot protocol on manta (Nexus 10)
|
34
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4ee0", MODE="0600", OWNER="hive"
|
35
|
+
# moto G 22b8:2e76
|
36
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="22b8", ATTR{idProduct}=="2e76", MODE="0666", OWNER="hive"
|
37
|
+
# Oneplus One
|
38
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="05c6", ATTR{idProduct}=="6765", MODE="0666", OWNER="hive"
|
39
|
+
# Tesco Hudl
|
40
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0e79", ATTR{idProduct}=="5009", MODE="0666", OWNER="hive"
|
41
|
+
# Nexus 7 (Grouper)
|
42
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4e42", MODE="0666", OWNER="hive"
|
43
|
+
# Nexus 7 (Flo)
|
44
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", ATTR{idProduct}=="4ee2", MODE="0666", OWNER="hive"
|
45
|
+
# Kindle Fire HD 8.9"
|
46
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="0008", MODE="0666", OWNER="hive"
|
47
|
+
# Kindle Fire
|
48
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="000b", MODE="0666", OWNER="hive"
|
49
|
+
# Kindle Fire
|
50
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="000c", MODE="0666", OWNER="hive"
|
51
|
+
# Kindle Fire (Pink Case)
|
52
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="00f2", MODE="0666", OWNER="hive"
|
53
|
+
# Sony Ericsson ST25i
|
54
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0fce", ATTR{idProduct}=="5171", MODE="0666", OWNER="hive"
|
55
|
+
# Galaxy S2/S3
|
56
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", ATTR{idProduct}=="6860", MODE="0666", OWNER="hive"
|
57
|
+
# LG G2
|
58
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="1004", ATTR{idProduct}=="631f", MODE="0666", OWNER="hive"
|
59
|
+
# Tesco Hudl 2
|
60
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="1d4d", ATTR{idProduct}=="504b", MODE="0666", OWNER="hive"
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
device_api (1.0.0)
|
5
|
+
diff-lcs (1.2.5)
|
6
|
+
rspec (3.0.0)
|
7
|
+
rspec-core (~> 3.0.0)
|
8
|
+
rspec-expectations (~> 3.0.0)
|
9
|
+
rspec-mocks (~> 3.0.0)
|
10
|
+
rspec-core (3.0.4)
|
11
|
+
rspec-support (~> 3.0.0)
|
12
|
+
rspec-expectations (3.0.4)
|
13
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
14
|
+
rspec-support (~> 3.0.0)
|
15
|
+
rspec-mocks (3.0.4)
|
16
|
+
rspec-support (~> 3.0.0)
|
17
|
+
rspec-support (3.0.4)
|
18
|
+
|
19
|
+
PLATFORMS
|
20
|
+
ruby
|
21
|
+
|
22
|
+
DEPENDENCIES
|
23
|
+
device_api (>= 1.0.0)
|
24
|
+
rspec
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 BBC
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
# DeviceAPI-Android
|
2
|
+
|
3
|
+
*DeviceAPI-Android* is the android implementation of device_api -- an initiative to allow full automation of device activities.
|
4
|
+
|
5
|
+
## Dependencies
|
6
|
+
|
7
|
+
device_api-android shells out to a number of android command line tools. You will need to make sure the android sdk is installed and you have the following commands on your path:
|
8
|
+
|
9
|
+
* adb
|
10
|
+
* aapt
|
11
|
+
|
12
|
+
## Using the gem
|
13
|
+
|
14
|
+
Add the device_api-android gem to your gemfile -- this will automatically bring in the device_api base gem on which the android gem is built.
|
15
|
+
|
16
|
+
gem 'device_api-android'
|
17
|
+
|
18
|
+
You'll need to require the library in your code:
|
19
|
+
|
20
|
+
require 'device_api/android'
|
21
|
+
|
22
|
+
Try connecting an android device with usb, and run:
|
23
|
+
|
24
|
+
devices = DeviceAPI::Android.devices
|
25
|
+
|
26
|
+
You might need to set your device to developer mode, and turn on usb debugging so that the android debug bridge can detect your device.
|
27
|
+
|
28
|
+
### Detecting devices
|
29
|
+
|
30
|
+
There are two methods for detecting devices:
|
31
|
+
|
32
|
+
DeviceAPI::Android.devices
|
33
|
+
|
34
|
+
This returns an array of objects representing the connected devices. You get an empty array if there are no connected devices.
|
35
|
+
|
36
|
+
DeviceAPI::Android.device(serial_id)
|
37
|
+
|
38
|
+
This looks for a device with a matching serial_id and returns a single device object.
|
39
|
+
|
40
|
+
### Device object
|
41
|
+
|
42
|
+
When device-api detects a device, it returns a device object that lets you interact with and query the device with various android tools.
|
43
|
+
|
44
|
+
For example:
|
45
|
+
|
46
|
+
device = DeviceAPI::Android.device(serial_id)
|
47
|
+
device.serial # "01498A0004005015"
|
48
|
+
device.model # "Galaxy Nexus"
|
49
|
+
|
50
|
+
#### Device orientation
|
51
|
+
|
52
|
+
device.orientation # :landscape / :portrait
|
53
|
+
|
54
|
+
#### Install/uninstall apk
|
55
|
+
|
56
|
+
device.install('location/apk_to_install.apk') # will install the apk on the device
|
57
|
+
device.uninstall('my.package.name') # will uninstall the package matching the package name
|
58
|
+
|
59
|
+
#### APK Signing
|
60
|
+
|
61
|
+
An APK can be signed using *DeviceAPI*. To do so you can simply run:
|
62
|
+
|
63
|
+
DeviceAPI::Android::Signing.sign_apk({apk: apk_path, resign: true})
|
64
|
+
|
65
|
+
If you don't already have a keystore setup then one will be created for you with some defaults already set. If you wish to setup a keystore using your own options you can do so using something like the following:
|
66
|
+
|
67
|
+
DeviceAPI::Android::Signing.generate_keystore( { keystore: '~/new_kestore.keystore', password: 'new_password' } )
|
68
|
+
|
69
|
+
This allows you to setup a keystore with the options required by any testing framework
|
70
|
+
|
71
|
+
### Package details
|
72
|
+
|
73
|
+
device.package_name('app.apk') # returns some.package.name
|
74
|
+
device.app_version_number('app.apk') # returns v#.#.#
|
75
|
+
|
76
|
+
## Testing
|
77
|
+
|
78
|
+
device_api-android is defended with unit and integration level rspec tests. You can run the tests with:
|
79
|
+
bundle exec rspec
|
80
|
+
|
81
|
+
## Issues
|
82
|
+
|
83
|
+
If you plug in a device and adb shows the device as having no permissions as seen here:
|
84
|
+
|
85
|
+
hive@hive-04:~$ adb devices
|
86
|
+
List of devices attached
|
87
|
+
???????????? no permissions
|
88
|
+
|
89
|
+
This is caused by the current user not having permission to access the USB interface. To resolve this, copy the 51-android.rules file to the /etc/udev/rules.d/ directory and restart adb by using the folliowing command
|
90
|
+
|
91
|
+
adb kill-server
|
92
|
+
adb start-server
|
93
|
+
|
94
|
+
If, after copying the rules file to the correct location, you're still seeing the no permission message it may be due to the fact that the device does not have a rule setup for it. To add a new rule, type:
|
95
|
+
|
96
|
+
lsusb
|
97
|
+
|
98
|
+
You should be presented with something similar to this:
|
99
|
+
|
100
|
+
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
|
101
|
+
Bus 001 Device 020: ID 0e79:5009 Archos, Inc.
|
102
|
+
Bus 001 Device 003: ID 05ac:8242 Apple, Inc. Built-in IR Receiver
|
103
|
+
Bus 001 Device 006: ID 05ac:8289 Apple, Inc.
|
104
|
+
Bus 001 Device 002: ID 0a5c:4500 Broadcom Corp. BCM2046B1 USB 2.0 Hub (part of BCM2046 Bluetooth)
|
105
|
+
Bus 001 Device 011: ID 05c6:6765 Qualcomm, Inc.
|
106
|
+
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
|
107
|
+
|
108
|
+
The important thing to note here is the Vendor ID and Product ID for the device. In the case of the above, the device is a Tesco Hudl (showing as an Archos device) with the combinded ID of 0e79:5009 - 0e79 is the Vendor ID while 5009 is the Product ID. Open the 51-android.rules file and add the following line:
|
109
|
+
|
110
|
+
SUBSYSTEM=="usb", ATTR{idVendor}=="0e79", ATTR{idProduct}=="5009", MODE="0666", OWNER="hive"
|
111
|
+
|
112
|
+
Change the Vendor and Product IDs where appropriate, also check that the owner matches the name of the account that will be running the Hive.
|
113
|
+
|
114
|
+
## License
|
115
|
+
|
116
|
+
*DeviceAPI-Android* is available to everyone under the terms of the MIT open source licence. Take a look at the LICENSE file in the code.
|
117
|
+
|
118
|
+
Copyright (c) 2015 BBC
|
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'device_api-android'
|
3
|
+
s.version = '1.0.1'
|
4
|
+
s.date = Time.now.strftime("%Y-%m-%d")
|
5
|
+
s.summary = 'Android Device Management API'
|
6
|
+
s.description = 'Android implementation of DeviceAPI'
|
7
|
+
s.authors = ['BBC','David Buckhurst','Jitesh Gosai', 'Jon Wilson']
|
8
|
+
s.email = 'david.buckhurst@bbc.co.uk'
|
9
|
+
s.files = `git ls-files`.split "\n"
|
10
|
+
s.homepage = 'https://github.com/bbc/device_api-android'
|
11
|
+
s.license = 'MIT'
|
12
|
+
s.add_runtime_dependency 'device_api', '>=1.0', '<2.0'
|
13
|
+
s.add_development_dependency 'rspec'
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
require 'device_api/android/adb'
|
3
|
+
require 'device_api/android/device'
|
4
|
+
require 'device_api/android/signing'
|
5
|
+
|
6
|
+
module DeviceAPI
|
7
|
+
module Android
|
8
|
+
# Returns array of connected android devices
|
9
|
+
def self.devices
|
10
|
+
ADB.devices.map do |d|
|
11
|
+
if d.keys.first && !d.keys.first.include?('?')
|
12
|
+
DeviceAPI::Android::Device.new(serial: d.keys.first, state: d.values.first)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# Retrieve an Device object by serial id
|
18
|
+
def self.device(serial)
|
19
|
+
if serial.to_s.empty?
|
20
|
+
raise DeviceAPI::BadSerialString.new("serial was '#{serial.nil? ? 'nil' : serial}'")
|
21
|
+
end
|
22
|
+
state = ADB.get_state(serial)
|
23
|
+
DeviceAPI::Android::Device.new(serial: serial, state: state)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Serial error class
|
28
|
+
class BadSerialString < StandardError
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
require 'open3'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'device_api/execution'
|
5
|
+
|
6
|
+
# DeviceAPI - an interface to allow for automation of devices
|
7
|
+
module DeviceAPI
|
8
|
+
# Android component of DeviceAPI
|
9
|
+
module Android
|
10
|
+
# Namespace for all methods encapsulating aapt calls
|
11
|
+
class AAPT < DeviceAPI::Execution
|
12
|
+
|
13
|
+
# Check to ensure that aapt has been setup correctly and is available
|
14
|
+
# @return (Boolean) true if aapt is available, false otherwise
|
15
|
+
def self.aapt_available?
|
16
|
+
result = execute('which aapt')
|
17
|
+
result.exit == 0
|
18
|
+
end
|
19
|
+
|
20
|
+
# Gets properties from the apk and returns them in a hash
|
21
|
+
# @param apk path to the apk
|
22
|
+
# @return (Hash) list of properties from the apk
|
23
|
+
def self.get_app_props(apk)
|
24
|
+
raise StandardError.new('aapt not found - please create a symlink in $ANDROID_HOME/tools') unless aapt_available?
|
25
|
+
result = execute("aapt dump badging #{apk}")
|
26
|
+
|
27
|
+
fail result.stderr if result.exit != 0
|
28
|
+
|
29
|
+
lines = result.stdout.split("\n")
|
30
|
+
results = []
|
31
|
+
lines.each do |l|
|
32
|
+
if /(.*): (.*)/.match(l)
|
33
|
+
# results.push(Regexp.last_match[1].strip => Regexp.last_match[2].strip)
|
34
|
+
values = {}
|
35
|
+
|
36
|
+
Regexp.last_match[2].strip.split(' ').each do |item| # split on an spaces
|
37
|
+
item = item.to_s.tr('\'', '') # trim off any excess single quotes
|
38
|
+
values[item.split('=')[0]] = item.split('=')[1] # split on the = and create a new hash
|
39
|
+
end
|
40
|
+
|
41
|
+
results << {Regexp.last_match[1].strip => values} # append the result tp new_result
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
results
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
# TODO: create new class for aapt that will get the package name from an apk using: JitG
|
3
|
+
# aapt dump badging packages/bbciplayer-debug.apk
|
4
|
+
require 'open3'
|
5
|
+
require 'ostruct'
|
6
|
+
require 'device_api/execution'
|
7
|
+
|
8
|
+
# DeviceAPI - an interface to allow for automation of devices
|
9
|
+
module DeviceAPI
|
10
|
+
# Android component of DeviceAPI
|
11
|
+
module Android
|
12
|
+
# Namespace for all methods encapsulating adb calls
|
13
|
+
class ADB < Execution
|
14
|
+
# Returns an array representing connected devices
|
15
|
+
# DeviceAPI::ADB.devices #=> { '1232132' => 'device' }
|
16
|
+
# @return (Array) list of attached devices
|
17
|
+
def self.devices
|
18
|
+
result = execute_with_timeout_and_retry('adb devices')
|
19
|
+
|
20
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
21
|
+
|
22
|
+
lines = result.stdout.split("\n")
|
23
|
+
results = []
|
24
|
+
|
25
|
+
lines.shift # Drop the message line
|
26
|
+
lines.each do |l|
|
27
|
+
if /(.*)\t(.*)/.match(l)
|
28
|
+
results.push(Regexp.last_match[1].strip => Regexp.last_match[2].strip)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
results
|
32
|
+
end
|
33
|
+
|
34
|
+
# Retrieve device state for a single device
|
35
|
+
# @param serial serial number of device
|
36
|
+
# @return (String) device state
|
37
|
+
def self.get_state(serial)
|
38
|
+
result = execute('adb get-state -s #{serial}')
|
39
|
+
|
40
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
41
|
+
|
42
|
+
lines = result.stdout.split("\n")
|
43
|
+
/(.*)/.match(lines.last)
|
44
|
+
Regexp.last_match[0].strip
|
45
|
+
end
|
46
|
+
|
47
|
+
# Get the properties of a specified device
|
48
|
+
# @param serial serial number of device
|
49
|
+
# @return (Hash) hash containing device properties
|
50
|
+
def self.getprop(serial)
|
51
|
+
result = execute("adb -s #{serial} shell getprop")
|
52
|
+
|
53
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
54
|
+
|
55
|
+
lines = result.stdout.split("\n")
|
56
|
+
|
57
|
+
props = {}
|
58
|
+
lines.each do |l|
|
59
|
+
if /\[(.*)\]:\s+\[(.*)\]/.match(l)
|
60
|
+
props[Regexp.last_match[1]] = Regexp.last_match[2]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
props
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the 'input' information from dumpsys
|
67
|
+
# @param serial serial number of device
|
68
|
+
# @return (Hash) hash containing input information from dumpsys
|
69
|
+
def self.getdumpsys(serial)
|
70
|
+
lines = dumpsys(serial, 'input')
|
71
|
+
|
72
|
+
props = {}
|
73
|
+
lines.each do |l|
|
74
|
+
if /(.*):\s+(.*)/.match(l)
|
75
|
+
props[Regexp.last_match[1]] = Regexp.last_match[2]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
props
|
79
|
+
end
|
80
|
+
|
81
|
+
# Get the 'iphonesubinfo' from dumpsys
|
82
|
+
# @param serial serial number of device
|
83
|
+
# @return (Hash) hash containing iphonesubinfo information from dumpsys
|
84
|
+
def self.getphoneinfo(serial)
|
85
|
+
lines = dumpsys(serial, 'iphonesubinfo')
|
86
|
+
|
87
|
+
props = {}
|
88
|
+
lines.each do |l|
|
89
|
+
if /(.*) =\s+(.*)/.match(l)
|
90
|
+
props[Regexp.last_match[1]] = Regexp.last_match[2]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
props
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns the 'dumpsys' information from the specified device
|
97
|
+
# @param serial serial number of device
|
98
|
+
# @return (Array) array of results from adb shell dumpsys
|
99
|
+
def self.dumpsys(serial, command)
|
100
|
+
result = execute("adb -s #{serial} shell dumpsys #{command}")
|
101
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
102
|
+
result.stdout.split("\n").map { |line| line.strip }
|
103
|
+
end
|
104
|
+
|
105
|
+
# Installs a specified apk to a specific device
|
106
|
+
# @param [Hash] options the options used for installing an apk
|
107
|
+
# @option options [String] :apk path to apk to install
|
108
|
+
# @option options [String] :serial serial number of device
|
109
|
+
# @return (String) return result from adb install command
|
110
|
+
def self.install_apk(options = {})
|
111
|
+
apk = options[:apk]
|
112
|
+
serial = options[:serial]
|
113
|
+
result = execute("adb -s #{serial} install #{apk}")
|
114
|
+
|
115
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
116
|
+
|
117
|
+
lines = result.stdout.split("\n").map { |line| line.strip }
|
118
|
+
# lines.each do |line|
|
119
|
+
# res=:success if line=='Success'
|
120
|
+
# end
|
121
|
+
|
122
|
+
lines.last
|
123
|
+
end
|
124
|
+
|
125
|
+
# Uninstalls a specified package from a specified device
|
126
|
+
# @param [Hash] options the options used for uninstalling a package
|
127
|
+
# @option options [String] :package_name package to uninstall
|
128
|
+
# @option options [String] :serial serial number of device
|
129
|
+
# @return (String) return result from adb uninstall command
|
130
|
+
def self.uninstall_apk(options = {})
|
131
|
+
package_name = options[:package_name]
|
132
|
+
serial = options[:serial]
|
133
|
+
result = execute("adb -s #{serial} uninstall #{package_name}")
|
134
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
135
|
+
|
136
|
+
lines = result.stdout.split("\n").map { |line| line.strip }
|
137
|
+
|
138
|
+
lines.last
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns the uptime of the specified device
|
142
|
+
# @param serial serial number of device
|
143
|
+
# @return (Float) uptime in seconds
|
144
|
+
def self.get_uptime(serial)
|
145
|
+
result = execute("adb -s #{serial} shell cat /proc/uptime")
|
146
|
+
|
147
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
148
|
+
|
149
|
+
lines = result.stdout.split("\n")
|
150
|
+
uptime = 0
|
151
|
+
lines.each do |l|
|
152
|
+
if /([\d.]*)\s+[\d.]*/.match(l)
|
153
|
+
uptime = Regexp.last_match[0].to_f.round
|
154
|
+
end
|
155
|
+
end
|
156
|
+
uptime
|
157
|
+
end
|
158
|
+
|
159
|
+
# Reboots the specified device
|
160
|
+
# @param serial serial number of device
|
161
|
+
# @return (nil) Nil if successful, otherwise an error is raised
|
162
|
+
def self.reboot(serial)
|
163
|
+
result = execute("adb -s #{serial} reboot")
|
164
|
+
raise ADBCommandError.new(result.stderr) if result.exit != 0
|
165
|
+
end
|
166
|
+
|
167
|
+
# Runs monkey testing
|
168
|
+
# @param serial serial number of device
|
169
|
+
# @param [Hash] args hash of arguments used for starting testing
|
170
|
+
# @option args [String] :events (10000) number of events to run
|
171
|
+
# @option args [String] :package name of package to run the tests against
|
172
|
+
# @option args [String] :seed pass the seed number (optional)
|
173
|
+
# @option args [String] :throttle throttle value (optional)
|
174
|
+
# @example
|
175
|
+
# DeviceAPI::ADB.monkey( serial, :package => 'my.lovely.app' )
|
176
|
+
def self.monkey(serial, args)
|
177
|
+
|
178
|
+
events = args[:events] || 10000
|
179
|
+
package = args[:package] or raise "package name not provided (:package => 'bbc.iplayer')"
|
180
|
+
seed = args[:seed]
|
181
|
+
throttle = args[:throttle]
|
182
|
+
|
183
|
+
cmd = "adb -s #{serial} shell monkey -p #{package} -v #{events}"
|
184
|
+
cmd = cmd + " -s #{seed}" if seed
|
185
|
+
cmd = cmd + " -t #{throttle}" if throttle
|
186
|
+
|
187
|
+
execute(cmd)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Take a screenshot from the device
|
191
|
+
# @param serial serial number of device
|
192
|
+
# @param [Hash] args hash of arguments
|
193
|
+
# @option args [String] :filename name (with full path) required to save the image
|
194
|
+
# @example
|
195
|
+
# DeviceAPI::ADB.screenshot( serial, :filename => '/tmp/filename.png' )
|
196
|
+
def self.screencap( serial, args )
|
197
|
+
|
198
|
+
filename = args[:filename] or raise "filename not provided (:filename => '/tmp/myfile.png')"
|
199
|
+
|
200
|
+
convert_carriage_returns = %q{perl -pe 's/\x0D\x0A/\x0A/g'}
|
201
|
+
cmd = "adb -s #{serial} shell screencap -p | #{convert_carriage_returns} > #{filename}"
|
202
|
+
|
203
|
+
execute(cmd)
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
# ADB Error class
|
209
|
+
class ADBCommandError < StandardError
|
210
|
+
def initialize(msg)
|
211
|
+
super(msg)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
# Encoding: utf-8
|
2
|
+
require 'device_api/device'
|
3
|
+
require 'device_api/android/adb'
|
4
|
+
require 'device_api/android/aapt'
|
5
|
+
|
6
|
+
# DeviceAPI - an interface to allow for automation of devices
|
7
|
+
module DeviceAPI
|
8
|
+
# Android component of DeviceAPI
|
9
|
+
module Android
|
10
|
+
# Device class used for containing the accessors of the physical device information
|
11
|
+
class Device < DeviceAPI::Device
|
12
|
+
def initialize(options = {})
|
13
|
+
@serial = options[:serial]
|
14
|
+
@state = options[:state]
|
15
|
+
end
|
16
|
+
|
17
|
+
# Mapping of device status - used to provide a consistent status across platforms
|
18
|
+
# @return (String) common status string
|
19
|
+
def status
|
20
|
+
{
|
21
|
+
'device' => :ok,
|
22
|
+
'no device' => :dead,
|
23
|
+
'offline' => :offline
|
24
|
+
}[@state]
|
25
|
+
end
|
26
|
+
|
27
|
+
# Return the device range
|
28
|
+
# @return (String) device range string
|
29
|
+
def range
|
30
|
+
device = self.device
|
31
|
+
model = self.model
|
32
|
+
|
33
|
+
return device if device == model
|
34
|
+
"#{device}_#{model}"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Return the device type
|
38
|
+
# @return (String) device type string
|
39
|
+
def device
|
40
|
+
get_prop('ro.product.device')
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return the device model
|
44
|
+
# @return (String) device model string
|
45
|
+
def model
|
46
|
+
get_prop('ro.product.model')
|
47
|
+
end
|
48
|
+
|
49
|
+
# Return the device manufacturer
|
50
|
+
# @return (String) device manufacturer string
|
51
|
+
def manufacturer
|
52
|
+
get_prop('ro.product.manufacturer')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Return the Android OS version
|
56
|
+
# @return (String) device Android version
|
57
|
+
def version
|
58
|
+
get_prop('ro.build.version.release')
|
59
|
+
end
|
60
|
+
|
61
|
+
# Return the device orientation
|
62
|
+
# @return (String) current device orientation
|
63
|
+
def orientation
|
64
|
+
res = get_dumpsys('SurfaceOrientation')
|
65
|
+
|
66
|
+
case res
|
67
|
+
when '0'
|
68
|
+
:portrait
|
69
|
+
when '1', '3'
|
70
|
+
:landscape
|
71
|
+
when nil
|
72
|
+
fail StandardError, 'No output returned is there a device connected?', caller
|
73
|
+
else
|
74
|
+
fail StandardError, "Device orientation not returned got: #{res}.", caller
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Install a specified apk
|
79
|
+
# @param [String] apk string containing path to the apk to install
|
80
|
+
# @return [Symbol, Exception] :success when the apk installed successfully, otherwise an error is raised
|
81
|
+
def install(apk)
|
82
|
+
fail StandardError, 'No apk specified.', caller if apk.empty?
|
83
|
+
res = install_apk(apk)
|
84
|
+
|
85
|
+
case res
|
86
|
+
when 'Success'
|
87
|
+
:success
|
88
|
+
else
|
89
|
+
fail StandardError, res, caller
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Uninstall a specified package
|
94
|
+
# @param [String] package_name name of the package to uninstall
|
95
|
+
# @return [Symbol, Exception] :success when the package is removed, otherwise an error is raised
|
96
|
+
def uninstall(package_name)
|
97
|
+
res = uninstall_apk(package_name)
|
98
|
+
case res
|
99
|
+
when 'Success'
|
100
|
+
:success
|
101
|
+
else
|
102
|
+
fail StandardError, "Unable to install 'package_name' Error Reported: #{res}", caller
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Return the package name for a specified apk
|
107
|
+
# @param [String] apk string containing path to the apk
|
108
|
+
# @return [String, Exception] package name if it can be found, otherwise an error is raised
|
109
|
+
def package_name(apk)
|
110
|
+
@apk = apk
|
111
|
+
result = get_app_props('package')['name']
|
112
|
+
fail StandardError, 'Package name not found', caller if result.nil?
|
113
|
+
result
|
114
|
+
end
|
115
|
+
|
116
|
+
# Return the app version number for a specified apk
|
117
|
+
# @param [String] apk string containing path to the apk
|
118
|
+
# @return [String, Exception] app version number if it can be found, otherwise an error is raised
|
119
|
+
def app_version_number(apk)
|
120
|
+
@apk = apk
|
121
|
+
result = get_app_props('package')['versionName']
|
122
|
+
fail StandardError, 'Version number not found', caller if result.nil?
|
123
|
+
result
|
124
|
+
end
|
125
|
+
|
126
|
+
# Initiate monkey tests
|
127
|
+
# @param [Hash] args arguments to pass on to ADB.monkey
|
128
|
+
def monkey(args)
|
129
|
+
ADB.monkey(serial, args)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Capture screenshot on device
|
133
|
+
# @param [Hash] args arguments to pass on to ADB.screencap
|
134
|
+
def screenshot(args)
|
135
|
+
ADB.screencap(serial, args)
|
136
|
+
end
|
137
|
+
|
138
|
+
# Get the IMEI number of the device
|
139
|
+
# @return (String) IMEI number of current device
|
140
|
+
def imei
|
141
|
+
get_phoneinfo['Device ID']
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def get_app_props(key)
|
147
|
+
unless @app_props
|
148
|
+
@app_props = AAPT.get_app_props(@apk)
|
149
|
+
end
|
150
|
+
@app_props.each { |x| break x[key] }
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_prop(key)
|
154
|
+
if !@props || !@props[key]
|
155
|
+
@props = ADB.getprop(serial)
|
156
|
+
end
|
157
|
+
@props[key]
|
158
|
+
end
|
159
|
+
|
160
|
+
def get_dumpsys(key)
|
161
|
+
@props = ADB.getdumpsys(serial)
|
162
|
+
@props[key]
|
163
|
+
end
|
164
|
+
|
165
|
+
def get_phoneinfo
|
166
|
+
ADB.getphoneinfo(serial)
|
167
|
+
end
|
168
|
+
|
169
|
+
def install_apk(apk)
|
170
|
+
ADB.install_apk(apk: apk, serial: serial)
|
171
|
+
end
|
172
|
+
|
173
|
+
def uninstall_apk(package_name)
|
174
|
+
ADB.uninstall_apk(package_name: package_name, serial: serial)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
File without changes
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# DeviceAPI - an interface to allow for automation of devices
|
2
|
+
module DeviceAPI
|
3
|
+
# Android component of DeviceAPI
|
4
|
+
module Android
|
5
|
+
# Namespace for all methods encapsulating adb calls
|
6
|
+
class Signing < Execution
|
7
|
+
|
8
|
+
# Creates a keystore used for signing apks
|
9
|
+
# @param [Hash] options options to pass through to keytool
|
10
|
+
# @option options [String] :keystore ('~/.android/debug.keystore') full path to location to create keystore
|
11
|
+
# @option options [String] :alias ('androiddebugkey') keystore alias name
|
12
|
+
# @option options [String] :dname ('CN=hive') keystore dname
|
13
|
+
# @option options [String] :password ('android') keystore password
|
14
|
+
# @return [Boolean, Exception] returns true if a keystore is created, otherwise an exception is raised
|
15
|
+
def self.generate_keystore(options = {})
|
16
|
+
keystore = options[:keystore] || '~/.android/debug.keystore'
|
17
|
+
alias_name = options[:alias] || 'androiddebugkey'
|
18
|
+
dname = options[:dname] || 'CN=hive'
|
19
|
+
password = options[:password] || 'android'
|
20
|
+
|
21
|
+
result = execute("keytool -genkey -noprompt -alias #{alias_name} -dname '#{dname}' -keystore #{keystore} -storepass #{password} -keypass #{password} -keyalg RSA -keysize 2048 -validity 10000")
|
22
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
# Signs an apk using the specified keystore
|
27
|
+
# @param [Hash] options options to pass through to jarsigner
|
28
|
+
# @option options [String] :apk full path to the apk to sign
|
29
|
+
# @option options [String] :alias ('androiddebugkey') alias of the keystore
|
30
|
+
# @option options [String] :keystore ('~/.android/debug.keystore') full path to the location of the keystore
|
31
|
+
# @option options [String] :keystore_password ('android') password required to open the keystore
|
32
|
+
# @option options [Boolean] :resign if true then an already signed apk will be stripped of previous signing and resigned
|
33
|
+
# @return [Boolean, Exception] return true if the apk is signed, false if the apk is already signed and resigning is anything other than true
|
34
|
+
# otherwise an exception is raised
|
35
|
+
def self.sign_apk(options = {})
|
36
|
+
apk = options[:apk]
|
37
|
+
alias_name = options[:alias] || 'androiddebugkey'
|
38
|
+
keystore = options[:keystore] || '~/.android/debug.keystore'
|
39
|
+
keystore_password = options[:keystore_password] || 'android'
|
40
|
+
resign = options[:resign]
|
41
|
+
|
42
|
+
# Check to see if the APK has already been signed
|
43
|
+
if is_apk_signed?(apk)
|
44
|
+
return false unless resign
|
45
|
+
unsign_apk(apk)
|
46
|
+
end
|
47
|
+
generate_keystore({ keystore: keystore, password: keystore_password, alias_name: alias_name }) unless File.exists?(File.expand_path(keystore))
|
48
|
+
result = execute("jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore #{File.expand_path(keystore)} -storepass #{keystore_password} #{apk} #{alias_name}")
|
49
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
50
|
+
true
|
51
|
+
end
|
52
|
+
|
53
|
+
# Checks to see if an apk has already been signed
|
54
|
+
# @param [String] apk_path full path to apk to check
|
55
|
+
# @return returns false if the apk is unsigned, true if it is signed
|
56
|
+
def self.is_apk_signed?(apk_path)
|
57
|
+
result = execute("aapt list #{apk_path} | grep '^META-INF.*\.RSA$'")
|
58
|
+
return false if result.stdout.empty?
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Removes any previous signatures from an apk
|
63
|
+
# @param [String] apk_path full path to the apk
|
64
|
+
# @return [Boolean, Exception] returns true if the apk is successfully unsigned, otherwise an exception is raised
|
65
|
+
def self.unsign_apk(apk_path)
|
66
|
+
file_list = execute("aapt list #{apk_path} | grep '^META-INF.*\.RSA$'")
|
67
|
+
result = execute("aapt remove #{apk_path} #{file_list.stdout.split(/\s+/).join(' ')}")
|
68
|
+
raise SigningCommandError.new(result.stderr) if result.exit != 0
|
69
|
+
true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Signing error class
|
74
|
+
class SigningCommandError < StandardError
|
75
|
+
def initialize(msg)
|
76
|
+
super(msg)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/spec/adb_spec.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
$LOAD_PATH.unshift('./lib/')
|
2
|
+
|
3
|
+
require 'device_api'
|
4
|
+
require 'device_api/android/adb'
|
5
|
+
|
6
|
+
include RSpec
|
7
|
+
|
8
|
+
#
|
9
|
+
#
|
10
|
+
# FIRST
|
11
|
+
describe DeviceAPI::Android::ADB do
|
12
|
+
describe '.devices' do
|
13
|
+
|
14
|
+
it 'returns an empty array when there are no devices' do
|
15
|
+
out = <<eos
|
16
|
+
List of devices attached
|
17
|
+
|
18
|
+
|
19
|
+
eos
|
20
|
+
allow(Open3).to receive(:capture3) {
|
21
|
+
[out, '', $STATUS_ZERO]
|
22
|
+
}
|
23
|
+
expect(DeviceAPI::Android::ADB.devices).to eq([])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "returns an array with a single item when there's one device attached" do
|
27
|
+
out = <<_______________________________________________________
|
28
|
+
List of devices attached
|
29
|
+
SH34RW905290 device
|
30
|
+
|
31
|
+
_______________________________________________________
|
32
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
33
|
+
expect(DeviceAPI::Android::ADB.devices).to eq([{ 'SH34RW905290' => 'device' }])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'returns an an array with multiple items when there are multiple items attached' do
|
37
|
+
out = <<_______________________________________________________
|
38
|
+
List of devices attached
|
39
|
+
SH34RW905290 device
|
40
|
+
123456324 no device
|
41
|
+
|
42
|
+
_______________________________________________________
|
43
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
44
|
+
expect(DeviceAPI::Android::ADB.devices).to eq([{ 'SH34RW905290' => 'device' }, { '123456324' => 'no device' }])
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'can deal with extra output when adb starts up' do
|
48
|
+
out = <<_______________________________________________________
|
49
|
+
* daemon not running. starting it now on port 5037 *
|
50
|
+
* daemon started successfully *
|
51
|
+
List of devices attached
|
52
|
+
SH34RW905290 device
|
53
|
+
_______________________________________________________
|
54
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
55
|
+
expect(DeviceAPI::Android::ADB.devices).to eq([{ 'SH34RW905290' => 'device' }])
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'can deal with no devices connected' do
|
59
|
+
allow(Open3).to receive(:capture3) { ["error: device not found\n", '', $STATUS_ZERO] }
|
60
|
+
expect(DeviceAPI::Android::ADB.devices).to be_empty
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe ".get_uptime" do
|
65
|
+
it "can process an uptime" do
|
66
|
+
out = <<_______________________________________________________
|
67
|
+
12307.23 48052.0
|
68
|
+
_______________________________________________________
|
69
|
+
allow(Open3).to receive(:capture3) { [ out, '', $STATUS_ZERO] }
|
70
|
+
expect( DeviceAPI::Android::ADB.get_uptime('SH34RW905290')).to eq( 12307 )
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe ".getprop" do
|
75
|
+
|
76
|
+
it "Returns a hash of name value pair properties" do
|
77
|
+
out = <<________________________________________________________
|
78
|
+
[net.hostname]: [android-f1e4efe3286b0785]
|
79
|
+
[dhcp.wlan0.ipaddress]: [10.0.1.34]
|
80
|
+
[ro.build.version.release]: [4.1.2]
|
81
|
+
[ro.build.version.sdk]: [16]
|
82
|
+
[ro.product.bluetooth]: [4.0]
|
83
|
+
[ro.product.device]: [m7]
|
84
|
+
[ro.product.display_resolution]: [4.7 inch 1080p resolution]
|
85
|
+
[ro.product.manufacturer]: [HTC]
|
86
|
+
[ro.product.model]: [HTC One]
|
87
|
+
[ro.product.name]: [m7]
|
88
|
+
[ro.product.processor]: [Quadcore]
|
89
|
+
[ro.product.ram]: [2GB]
|
90
|
+
[ro.product.version]: [1.28.161.7]
|
91
|
+
[ro.product.wifi]: [802.11 a/b/g/n/ac]
|
92
|
+
[ro.revision]: [3]
|
93
|
+
[ro.serialno]: [SH34RW905290]
|
94
|
+
[ro.sf.lcd_density]: [480]
|
95
|
+
________________________________________________________
|
96
|
+
|
97
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
98
|
+
|
99
|
+
props = DeviceAPI::Android::ADB.getprop('SH34RW905290')
|
100
|
+
|
101
|
+
expect(props).to be_a Hash
|
102
|
+
expect(props['ro.product.model']).to eq('HTC One')
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe ".get_status" do
|
107
|
+
|
108
|
+
it "Returns a state for a single device" do
|
109
|
+
out = <<_______________________________________________________
|
110
|
+
device
|
111
|
+
_______________________________________________________
|
112
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
113
|
+
|
114
|
+
state = DeviceAPI::Android::ADB.get_state('SH34RW905290')
|
115
|
+
|
116
|
+
expect(state).to eq 'device'
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe ".monkey" do
|
121
|
+
|
122
|
+
it "Constructs and executes monkey command line" do
|
123
|
+
out = <<_______________________________________________________
|
124
|
+
** Monkey aborted due to error.
|
125
|
+
Events injected: 3082
|
126
|
+
:Sending rotation degree=0, persist=false
|
127
|
+
:Dropped: keys=88 pointers=180 trackballs=0 flips=0 rotations=0
|
128
|
+
## Network stats: elapsed time=14799ms (0ms mobile, 0ms wifi, 14799ms not connected)
|
129
|
+
** System appears to have crashed at event 3082 of 5000000 using seed 1409644708681
|
130
|
+
end
|
131
|
+
_______________________________________________________
|
132
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
133
|
+
|
134
|
+
expect( DeviceAPI::Android::ADB.monkey( '1234323', :events => 5000, :package => 'my.app.package' )).to be_a OpenStruct
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'device_api/android'
|
2
|
+
include RSpec
|
3
|
+
|
4
|
+
describe DeviceAPI::Android do
|
5
|
+
|
6
|
+
describe '.devices' do
|
7
|
+
|
8
|
+
it 'Returns an empty array when no devices are connected' do
|
9
|
+
out = <<_______________________________________________________
|
10
|
+
List of devices attached
|
11
|
+
|
12
|
+
_______________________________________________________
|
13
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
14
|
+
expect(DeviceAPI::Android.devices).to eq([])
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns an array with a single item when there's one device attached" do
|
18
|
+
out = <<_______________________________________________________
|
19
|
+
List of devices attached
|
20
|
+
SH34RW905290 device
|
21
|
+
|
22
|
+
_______________________________________________________
|
23
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
24
|
+
|
25
|
+
devices = DeviceAPI::Android.devices
|
26
|
+
|
27
|
+
expect(devices.count).to eq(1)
|
28
|
+
expect(devices[0]).to be_a DeviceAPI::Android::Device
|
29
|
+
expect(devices[0].serial).to eq('SH34RW905290')
|
30
|
+
expect(devices[0].status).to eq(:ok)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ".device" do
|
35
|
+
|
36
|
+
it "Returns an object representing a device" do
|
37
|
+
out = <<_______________________________________________________
|
38
|
+
device
|
39
|
+
_______________________________________________________
|
40
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
41
|
+
|
42
|
+
device = DeviceAPI::Android.device('SH34RW905290')
|
43
|
+
expect(device).to be_a DeviceAPI::Android::Device
|
44
|
+
expect(device.serial).to eq('SH34RW905290')
|
45
|
+
expect(device.status).to eq(:ok)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'device_api/android/device'
|
2
|
+
include RSpec
|
3
|
+
|
4
|
+
describe DeviceAPI::Android::Device do
|
5
|
+
|
6
|
+
describe '.model' do
|
7
|
+
|
8
|
+
it 'Returns model name' do
|
9
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
10
|
+
|
11
|
+
allow(Open3).to receive(:capture3) { ['[ro.product.model]: [HTC One]\n', '', $STATUS_ZERO] }
|
12
|
+
expect(device.model).to eq('HTC One')
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '.orientation' do
|
18
|
+
it 'Returns portrait when device is portrait' do
|
19
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
20
|
+
allow(Open3).to receive(:capture3) { ["SurfaceOrientation: 0\r\n", '', $STATUS_ZERO] }
|
21
|
+
|
22
|
+
expect(device.orientation).
|
23
|
+
to eq(:portrait)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'Returns landscape when device is landscape' do
|
27
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
28
|
+
allow(Open3).to receive(:capture3) { ["SurfaceOrientation: 1\r\n", '', $STATUS_ZERO] }
|
29
|
+
|
30
|
+
expect(device.orientation).
|
31
|
+
to eq(:landscape)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'Returns landscape when device is landscape for a kindle Fire' do
|
35
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
36
|
+
allow(Open3).to receive(:capture3) { ["SurfaceOrientation: 3\r\n", '', $STATUS_ZERO] }
|
37
|
+
|
38
|
+
expect(device.orientation).
|
39
|
+
to eq(:landscape)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'Returns an error if response not understood' do
|
43
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
44
|
+
|
45
|
+
allow(Open3).to receive(:capture3) { ["SurfaceOrientation: 564654654\n", '', $STATUS_ZERO] }
|
46
|
+
|
47
|
+
expect { device.orientation }.
|
48
|
+
to raise_error(StandardError, 'Device orientation not returned got: 564654654.')
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'Returns an error if no device found' do
|
52
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
53
|
+
|
54
|
+
allow(Open3).to receive(:capture3) { ["error: device not found\n", '', $STATUS_ZERO] }
|
55
|
+
|
56
|
+
expect { device.orientation }.
|
57
|
+
to raise_error(StandardError, 'No output returned is there a device connected?')
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'Can handle device orientation changes during a test' do
|
61
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
62
|
+
landscape = "SurfaceOrientation: 1\r\n"
|
63
|
+
portrait = "SurfaceOrientation: 0\r\n"
|
64
|
+
|
65
|
+
allow(Open3).to receive(:capture3) { [portrait, '', $STATUS_ZERO] }
|
66
|
+
expect(device.orientation).
|
67
|
+
to eq(:portrait)
|
68
|
+
allow(Open3).to receive(:capture3) { [landscape, '', $STATUS_ZERO] }
|
69
|
+
expect(device.orientation).
|
70
|
+
to eq(:landscape)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'Can filter on large amounts of adb output to find the correct value', type: 'adb' do
|
74
|
+
out = <<_______________________________________________________
|
75
|
+
uchMajor: min=0, max=15, flat=0, fuzz=0, resolution=0\r\n TouchMinor: unknown range\r\n
|
76
|
+
ToolMajor: unknown range\r\n ToolMinor: unknown range\r\n Orientation: unknown range\r\n
|
77
|
+
Distance: unknown range\r\n TiltX: unknown range\r\n TiltY: unknown range\r\n
|
78
|
+
TrackingId: min=0, max=65535, flat=0, fuzz=0, resolution=0\r\n Slot: min=0, max=9, flat=0, fuzz=0,
|
79
|
+
resolution=0\r\n Calibration:\r\n touch.size.calibration: diameter\r\n
|
80
|
+
touch.size.scale: 22.500\r\n touch.size.bias: 0.000\r\n touch.size.isSummed: false\r\n
|
81
|
+
touch.pressure.calibration: amplitude\r\n touch.pressure.scale: 0.013\r\n touch.orientation.calibration: none\r\n
|
82
|
+
touch.distance.calibration: none\r\n touch.coverage.calibration: none\r\n Viewport: displayId=0, orientation=0,
|
83
|
+
logicalFrame=[0, 0, 768, 1280], physicalFrame=[0, 0, 768, 1280], deviceSize=[768, 1280]\r\n SurfaceWidth: 768px\r\n
|
84
|
+
SurfaceHeight: 1280px\r\n SurfaceLeft: 0\r\n SurfaceTop: 0\r\n SurfaceOrientation: 0\r\n
|
85
|
+
Translation and Scaling Factors:\r\n XTranslate: 0.000\r\n YTranslate: 0.000\r\n XScale: 0.500\r\n
|
86
|
+
YScale: 0.500\r\n XPrecision: 2.000\r\n YPrecision: 2.000\r\n
|
87
|
+
_______________________________________________________
|
88
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
89
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
90
|
+
|
91
|
+
expect(device.orientation).
|
92
|
+
to eq(:portrait)
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '.install' do
|
99
|
+
|
100
|
+
it 'Can install an apk' do
|
101
|
+
out = <<_______________________________________________________
|
102
|
+
4458 KB/s (9967857 bytes in 2.183s)
|
103
|
+
pkg: /data/local/tmp/bbciplayer-debug.apk
|
104
|
+
Success
|
105
|
+
_______________________________________________________
|
106
|
+
|
107
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
108
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
109
|
+
expect(device.install('some_apk.spk')).
|
110
|
+
to eq(:success)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'Can display an error when the apk is not found' do
|
114
|
+
out = "can't find 'fake.apk' to install"
|
115
|
+
|
116
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
117
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
118
|
+
expect { device.install('fake.apk') }.
|
119
|
+
to raise_error(StandardError, "can't find 'fake.apk' to install")
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'Can display an error message when no apk is specified' do
|
123
|
+
out = 'No apk specified.'
|
124
|
+
|
125
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
126
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
127
|
+
expect { device.install('fake.apk') }.
|
128
|
+
to raise_error(StandardError, 'No apk specified.')
|
129
|
+
end
|
130
|
+
|
131
|
+
it 'Can display an error when the apk is already installed' do
|
132
|
+
out = 'Failure [INSTALL_FAILED_ALREADY_EXISTS]'
|
133
|
+
|
134
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
135
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
136
|
+
expect { device.install('fake.apk') }.
|
137
|
+
to raise_error(StandardError, 'Failure [INSTALL_FAILED_ALREADY_EXISTS]')
|
138
|
+
end
|
139
|
+
|
140
|
+
describe '.uninstall' do
|
141
|
+
|
142
|
+
it 'Can uninstall an apk' do
|
143
|
+
out = 'Success'
|
144
|
+
|
145
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
146
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
147
|
+
expect(device.uninstall('pack_name')).
|
148
|
+
to eq(:success)
|
149
|
+
end
|
150
|
+
|
151
|
+
it 'Can raise an error if the uninstall was unsuccessful' do
|
152
|
+
out = 'Failure'
|
153
|
+
|
154
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
155
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
156
|
+
expect { device.uninstall('pack_name') }.
|
157
|
+
to raise_error(StandardError, "Unable to install 'package_name' Error Reported: Failure")
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
describe '.package_name' do
|
163
|
+
out = "package: name='bbc.iplayer.android' versionCode='4200066' versionName='4.2.0.66'"
|
164
|
+
|
165
|
+
it 'Can get the package name from an apk' do
|
166
|
+
|
167
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
168
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
169
|
+
expect(device.package_name('iplayer.apk')).
|
170
|
+
to eq('bbc.iplayer.android')
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'Can get the version number from an apk' do
|
174
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
175
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
176
|
+
expect(device.app_version_number('iplayer.apk')).
|
177
|
+
to eq('4.2.0.66')
|
178
|
+
end
|
179
|
+
|
180
|
+
it 'can raise an error if the app package name is not found' do
|
181
|
+
out = "package: versionCode='4200066' versionName='4.2.0.66'"
|
182
|
+
|
183
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
184
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
185
|
+
expect { device.package_name('iplayer.apk') }.
|
186
|
+
to raise_error(StandardError, 'Package name not found')
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'can raise an error if the app version number is not found' do
|
190
|
+
out = "package: name='bbc.iplayer.android' yyyyy='xxxxxxxx' qqqqq='rrrrrrrr'"
|
191
|
+
|
192
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
193
|
+
allow(Open3).to receive(:capture3) { [out, '', $STATUS_ZERO] }
|
194
|
+
expect { device.app_version_number('iplayer.apk') }.
|
195
|
+
to raise_error(StandardError, 'Version number not found')
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'can raise an error if aapt can not be found' do
|
199
|
+
device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290')
|
200
|
+
allow(Open3).to receive(:capture3) { ['', '', $STATUS_ONE] }
|
201
|
+
expect { device.app_version_number('iplayer.apk') }.
|
202
|
+
to raise_error(StandardError, 'aapt not found - please create a symlink in $ANDROID_HOME/tools')
|
203
|
+
end
|
204
|
+
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: device_api-android
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- BBC
|
8
|
+
- David Buckhurst
|
9
|
+
- Jitesh Gosai
|
10
|
+
- Jon Wilson
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2015-05-22 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: device_api
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '1.0'
|
23
|
+
- - "<"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '2.0'
|
26
|
+
type: :runtime
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.0'
|
33
|
+
- - "<"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: '2.0'
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: '0'
|
43
|
+
type: :development
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
description: Android implementation of DeviceAPI
|
51
|
+
email: david.buckhurst@bbc.co.uk
|
52
|
+
executables: []
|
53
|
+
extensions: []
|
54
|
+
extra_rdoc_files: []
|
55
|
+
files:
|
56
|
+
- ".gitignore"
|
57
|
+
- 51-android.rules
|
58
|
+
- Gemfile
|
59
|
+
- Gemfile.lock
|
60
|
+
- LICENSE
|
61
|
+
- README.md
|
62
|
+
- device_api-android.gemspec
|
63
|
+
- lib/device_api/android.rb
|
64
|
+
- lib/device_api/android/aapt.rb
|
65
|
+
- lib/device_api/android/adb.rb
|
66
|
+
- lib/device_api/android/device.rb
|
67
|
+
- lib/device_api/android/devices/default.rb
|
68
|
+
- lib/device_api/android/signing.rb
|
69
|
+
- spec/adb_spec.rb
|
70
|
+
- spec/android_device_spec.rb
|
71
|
+
- spec/android_spec.rb
|
72
|
+
- spec/helpers/stub_helper_spec.rb
|
73
|
+
homepage: https://github.com/bbc/device_api-android
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.4.5
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Android Device Management API
|
97
|
+
test_files: []
|
98
|
+
has_rdoc:
|