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.
@@ -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
@@ -0,0 +1,2 @@
1
+ .idea
2
+
@@ -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
@@ -0,0 +1,7 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'device_api', '>=1.0.0'
4
+
5
+ group :test do
6
+ gem 'rspec'
7
+ end
@@ -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.
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ ProcessStatusStub = Struct.new(:exitstatus)
2
+ $STATUS_ZERO = ProcessStatusStub.new(0)
3
+ $STATUS_ONE = ProcessStatusStub.new(1)
4
+
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: