device_api-android 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: