device_api-android 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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:
|