├── .gitignore ├── .rspec ├── .travis.yml ├── 51-android.rules ├── CHANGELOG.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── device_api-android.gemspec ├── lib └── device_api │ ├── android.rb │ └── android │ ├── aapt.rb │ ├── adb.rb │ ├── device.rb │ ├── device │ ├── kindle.rb │ └── samsung.rb │ ├── plugins │ ├── audio.rb │ ├── battery.rb │ ├── disk.rb │ └── memory.rb │ └── signing.rb └── spec ├── android_audio_spec.rb ├── android_spec.rb ├── lib └── android │ ├── adb_spec.rb │ └── device_spec.rb └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .swp 3 | 4 | 5 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2 4 | - jruby 5 | 6 | script: bundle exec rspec 7 | 8 | addons: 9 | code_climate: 10 | repo_token: 11 | secure: "ezBFtg7kkdXPBsBTsZUYWuqPhfgPmRou3NJLnDOjRSkDDkLjmKEw5tSjQaM73KOt8/HXI9XSq0saGih/3/xwAdMj0sxGg03Xizlau48jc9FkvZbv1KEmtmdFM+DbhaKlHceColRSA/t1iM2GBd0iUoQxGFy3qk2NtiwcKpXyEn1lwIemLcKuzX8oOu/ZH6U3BS58EoF2cWR4c9IK/hSgDRjdZYIlWlaHzhJ1HVvy/bg3569GjcYg9FLlJ9rw7c1HqpcbKSJ/oJRjiCn9nGRQb0W2wVzysIMShyW7+sij9btNDloQPq4X8XESn6IYgTtu2ks8FoS4SNLn2y/0q6ma5Ws3yqDJ7hRKKSDepePxRcZBievpwcGvEpQ+RR1NDjtTmVn4UGGTXTAYLDJi3jfIFWWIAqZInwy7MFN5AArwvoHh1a8zSXfCRS/xZtFLIrZJ/yd9QL3AKwEQDoa8dtY9mpdoyObN9Gio9M3BGYUjKZvHm+HpdaCXOIqSAtrj3mQ4AMoZYdJsSyL5NTYcC42Jsy+n4Hl4an6OgwgXRmMkL0M1bo0HnGCGgDx9B28sNeNg5I0lYJBGzMG/1WNLkBjVh+2ZpqjlMXzrGpDC44Q3HQHS9V8qvKliWWfmbDpM0iQSLrDFyvdI6v/8KVEPZXARfPNzpTy7NHrCBXmvUzdGcT0=" 12 | -------------------------------------------------------------------------------- /51-android.rules: -------------------------------------------------------------------------------- 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 5th Gen" 46 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="0212", MODE="0666", OWNER="hive" 47 | # Kindle Fire HD 8.9" 48 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="0008", MODE="0666", OWNER="hive" 49 | # Kindle Fire 50 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="000b", MODE="0666", OWNER="hive" 51 | # Kindle Fire 52 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="000c", MODE="0666", OWNER="hive" 53 | # Kindle Fire (Pink Case) 54 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="00f2", MODE="0666", OWNER="hive" 55 | # Kindle Fire HDX 8.9 (3rd Generation) OS 4.5.5 56 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="000d", mode="0666", OWNER="hive" 57 | # Kindle Fire HD 10" 5th Generation 58 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="0202", MODE="0666", OWNER="hive" 59 | # Kindle Fire HD 7" 5th Generation 60 | SUBSYSTEM=="usb", ATTR{idVendor}=="1949", ATTR{idProduct}=="0222", MODE="0666", OWNER="hive" 61 | # Sony Ericsson ST25i 62 | SUBSYSTEM=="usb", ATTR{idVendor}=="0fce", ATTR{idProduct}=="5171", MODE="0666", OWNER="hive" 63 | # Galaxy S2/S3 64 | SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", ATTR{idProduct}=="6860", MODE="0666", OWNER="hive" 65 | # LG G2 66 | SUBSYSTEM=="usb", ATTR{idVendor}=="1004", ATTR{idProduct}=="631f", MODE="0666", OWNER="hive" 67 | # Tesco Hudl 2 68 | SUBSYSTEM=="usb", ATTR{idVendor}=="1d4d", ATTR{idProduct}=="504b", MODE="0666", OWNER="hive" 69 | # HTC Desire S 70 | SUBSYSTEM=="usb", ATTR{idVendor}=="0bb4", ATTR{idProduct}=="0cab", MODE="0666", OWNER="hive" 71 | # HTC One M8 72 | SUBSYSTEM=="usb", ATTR{idVendor}=="0bb4", ATTR{idProduct}=="0c81", MODE="0666", OWNER="hive" 73 | # Sony Experia Z5 74 | SUBSYSTEM=="usb", ATTR{idVendor}=="0fce", ATTR{idProduct}=="51d9", MODE="0666", OWNER="hive" 75 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | ## [v1.2.10](https://github.com/bbc/device_api-android/tree/v1.2.10) (2016-10-03) 4 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.9...v1.2.10) 5 | 6 | **Fixed bugs:** 7 | 8 | - Disconnect fails when serial is ipaddress:port [\#103](https://github.com/bbc/device_api-android/issues/103) 9 | 10 | **Closed issues:** 11 | 12 | - Reference to qualifiers and serials [\#106](https://github.com/bbc/device_api-android/issues/106) 13 | - Add android-tv as a device type [\#91](https://github.com/bbc/device_api-android/issues/91) 14 | - Need to track "protocol fault \(no status\)" error [\#67](https://github.com/bbc/device_api-android/issues/67) 15 | 16 | **Merged pull requests:** 17 | 18 | - Added checks for AAPT in methods that use it [\#113](https://github.com/bbc/device_api-android/pull/113) ([jonpwilson](https://github.com/jonpwilson)) 19 | - Added Sony Experia Z5 to 51-rules [\#111](https://github.com/bbc/device_api-android/pull/111) ([jonpwilson](https://github.com/jonpwilson)) 20 | - The lock is in a different position on older versions on Kindle [\#110](https://github.com/bbc/device_api-android/pull/110) ([jonpwilson](https://github.com/jonpwilson)) 21 | - Method to check whether device is connected [\#108](https://github.com/bbc/device_api-android/pull/108) ([Asimk21](https://github.com/Asimk21)) 22 | - Replaced serial references to qualifier [\#107](https://github.com/bbc/device_api-android/pull/107) ([Asimk21](https://github.com/Asimk21)) 23 | - Connect and disconnect remote android device [\#105](https://github.com/bbc/device_api-android/pull/105) ([Asimk21](https://github.com/Asimk21)) 24 | - Added changelog [\#104](https://github.com/bbc/device_api-android/pull/104) ([jonpwilson](https://github.com/jonpwilson)) 25 | 26 | ## [v1.2.9](https://github.com/bbc/device_api-android/tree/v1.2.9) (2016-08-09) 27 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.8...v1.2.9) 28 | 29 | **Closed issues:** 30 | 31 | - Amazon Kindle Fire 5th Gen is not unlocked when auto-rotate is off [\#100](https://github.com/bbc/device_api-android/issues/100) 32 | 33 | **Merged pull requests:** 34 | 35 | - Bumped version number [\#102](https://github.com/bbc/device_api-android/pull/102) ([jonpwilson](https://github.com/jonpwilson)) 36 | - Added orientation checking when unlocking a kindle [\#101](https://github.com/bbc/device_api-android/pull/101) ([jonpwilson](https://github.com/jonpwilson)) 37 | - Kindle fire HD 7" 5th Gen [\#99](https://github.com/bbc/device_api-android/pull/99) ([Asimk21](https://github.com/Asimk21)) 38 | - Remote android devices [\#98](https://github.com/bbc/device_api-android/pull/98) ([wordswords](https://github.com/wordswords)) 39 | - Update README.md [\#97](https://github.com/bbc/device_api-android/pull/97) ([jonpwilson](https://github.com/jonpwilson)) 40 | 41 | ## [v1.2.8](https://github.com/bbc/device_api-android/tree/v1.2.8) (2016-04-07) 42 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.7...v1.2.8) 43 | 44 | **Closed issues:** 45 | 46 | - netcfg removed in Android 6 [\#92](https://github.com/bbc/device_api-android/issues/92) 47 | 48 | **Merged pull requests:** 49 | 50 | - Wifi mac address for android 6 [\#96](https://github.com/bbc/device_api-android/pull/96) ([jonpwilson](https://github.com/jonpwilson)) 51 | - Changed the IP Address method to use ifconfig so it's more reliable o… [\#95](https://github.com/bbc/device_api-android/pull/95) ([jonpwilson](https://github.com/jonpwilson)) 52 | 53 | ## [v1.2.7](https://github.com/bbc/device_api-android/tree/v1.2.7) (2016-03-30) 54 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.6...v1.2.7) 55 | 56 | **Merged pull requests:** 57 | 58 | - Small typo [\#94](https://github.com/bbc/device_api-android/pull/94) ([jrmhaig](https://github.com/jrmhaig)) 59 | 60 | ## [v1.2.6](https://github.com/bbc/device_api-android/tree/v1.2.6) (2016-03-30) 61 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.5...v1.2.6) 62 | 63 | **Closed issues:** 64 | 65 | - Undefined method downcase [\#80](https://github.com/bbc/device_api-android/issues/80) 66 | 67 | **Merged pull requests:** 68 | 69 | - Add 'no permissions' state [\#93](https://github.com/bbc/device_api-android/pull/93) ([jrmhaig](https://github.com/jrmhaig)) 70 | 71 | ## [v1.2.5](https://github.com/bbc/device_api-android/tree/v1.2.5) (2016-02-24) 72 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.4...v1.2.5) 73 | 74 | **Merged pull requests:** 75 | 76 | - Ruby 1.9.3 fix [\#90](https://github.com/bbc/device_api-android/pull/90) ([jonpwilson](https://github.com/jonpwilson)) 77 | 78 | ## [v1.2.4](https://github.com/bbc/device_api-android/tree/v1.2.4) (2016-02-19) 79 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.3...v1.2.4) 80 | 81 | **Merged pull requests:** 82 | 83 | - Ensure detected devices list doesn't include nils [\#89](https://github.com/bbc/device_api-android/pull/89) ([jrmhaig](https://github.com/jrmhaig)) 84 | 85 | ## [v1.2.3](https://github.com/bbc/device_api-android/tree/v1.2.3) (2016-02-15) 86 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.2...v1.2.3) 87 | 88 | **Merged pull requests:** 89 | 90 | - Catch disconnects [\#88](https://github.com/bbc/device_api-android/pull/88) ([jrmhaig](https://github.com/jrmhaig)) 91 | 92 | ## [v1.2.2](https://github.com/bbc/device_api-android/tree/v1.2.2) (2016-02-09) 93 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.1...v1.2.2) 94 | 95 | **Closed issues:** 96 | 97 | - wifi\[:mac\] returns nil when wifi is turned off causing hive to crash. [\#82](https://github.com/bbc/device_api-android/issues/82) 98 | - Audio output level methods need adding [\#27](https://github.com/bbc/device_api-android/issues/27) 99 | - Add methods for returning extra device information [\#22](https://github.com/bbc/device_api-android/issues/22) 100 | 101 | **Merged pull requests:** 102 | 103 | - Raise exceptions when shell commands fail [\#87](https://github.com/bbc/device_api-android/pull/87) ([jrmhaig](https://github.com/jrmhaig)) 104 | - Tidy up gems and spec files a bit [\#86](https://github.com/bbc/device_api-android/pull/86) ([jrmhaig](https://github.com/jrmhaig)) 105 | - Audio functions [\#85](https://github.com/bbc/device_api-android/pull/85) ([jonpwilson](https://github.com/jonpwilson)) 106 | - Added check to ensure that the wlan0 and wifi are detected [\#84](https://github.com/bbc/device_api-android/pull/84) ([jonpwilson](https://github.com/jonpwilson)) 107 | - Htc rule [\#83](https://github.com/bbc/device_api-android/pull/83) ([Asimk21](https://github.com/Asimk21)) 108 | - Refactored ADB shell commands [\#77](https://github.com/bbc/device_api-android/pull/77) ([jonpwilson](https://github.com/jonpwilson)) 109 | 110 | ## [v1.2.1](https://github.com/bbc/device_api-android/tree/v1.2.1) (2015-11-27) 111 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.2.0...v1.2.1) 112 | 113 | **Closed issues:** 114 | 115 | - Power and screen state needs to be updated [\#78](https://github.com/bbc/device_api-android/issues/78) 116 | - Add Samsung 'bubble' killing script [\#65](https://github.com/bbc/device_api-android/issues/65) 117 | - Need to add check to see if an APK is currently installed [\#26](https://github.com/bbc/device_api-android/issues/26) 118 | 119 | **Merged pull requests:** 120 | 121 | - Added network information and test [\#81](https://github.com/bbc/device_api-android/pull/81) ([jonpwilson](https://github.com/jonpwilson)) 122 | - Changed the 'screen\_on?' check to work with Lollipop [\#79](https://github.com/bbc/device_api-android/pull/79) ([jonpwilson](https://github.com/jonpwilson)) 123 | 124 | ## [v1.2.0](https://github.com/bbc/device_api-android/tree/v1.2.0) (2015-10-19) 125 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.1.5...v1.2.0) 126 | 127 | **Merged pull requests:** 128 | 129 | - Added Samsung specific device to kill the multi window bubble \(fixes … [\#76](https://github.com/bbc/device_api-android/pull/76) ([jonpwilson](https://github.com/jonpwilson)) 130 | - Fixed serial call [\#75](https://github.com/bbc/device_api-android/pull/75) ([jonpwilson](https://github.com/jonpwilson)) 131 | - Added permissions for Kindle 5th Gen [\#74](https://github.com/bbc/device_api-android/pull/74) ([Asimk21](https://github.com/Asimk21)) 132 | - Fixed UTF-8 encoding for Ruby 1.9.3 [\#73](https://github.com/bbc/device_api-android/pull/73) ([jonpwilson](https://github.com/jonpwilson)) 133 | - Change the device\_type method so that it will return default if a dev… [\#72](https://github.com/bbc/device_api-android/pull/72) ([jonpwilson](https://github.com/jonpwilson)) 134 | 135 | ## [v1.1.5](https://github.com/bbc/device_api-android/tree/v1.1.5) (2015-10-14) 136 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.1.4...v1.1.5) 137 | 138 | **Merged pull requests:** 139 | 140 | - Utf8 [\#71](https://github.com/bbc/device_api-android/pull/71) ([jonpwilson](https://github.com/jonpwilson)) 141 | 142 | ## [v1.1.4](https://github.com/bbc/device_api-android/tree/v1.1.4) (2015-10-14) 143 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.1.3...v1.1.4) 144 | 145 | **Merged pull requests:** 146 | 147 | - Added test and status for handling untrusted devices [\#70](https://github.com/bbc/device_api-android/pull/70) ([jonpwilson](https://github.com/jonpwilson)) 148 | - Utf8 parsing [\#69](https://github.com/bbc/device_api-android/pull/69) ([jonpwilson](https://github.com/jonpwilson)) 149 | 150 | ## [v1.1.3](https://github.com/bbc/device_api-android/tree/v1.1.3) (2015-10-07) 151 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.1.2...v1.1.3) 152 | 153 | ## [v1.1.2](https://github.com/bbc/device_api-android/tree/v1.1.2) (2015-10-06) 154 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.1.1...v1.1.2) 155 | 156 | **Closed issues:** 157 | 158 | - Memory plugin needs a refresh method [\#50](https://github.com/bbc/device_api-android/issues/50) 159 | 160 | **Merged pull requests:** 161 | 162 | - Fix for keyevent results [\#68](https://github.com/bbc/device_api-android/pull/68) ([jonpwilson](https://github.com/jonpwilson)) 163 | 164 | ## [v1.1.1](https://github.com/bbc/device_api-android/tree/v1.1.1) (2015-09-15) 165 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.1.0...v1.1.1) 166 | 167 | ## [v1.1.0](https://github.com/bbc/device_api-android/tree/v1.1.0) (2015-09-15) 168 | [Full Changelog](https://github.com/bbc/device_api-android/compare/v1.0.0...v1.1.0) 169 | 170 | **Closed issues:** 171 | 172 | - Total Memory for kindle devices is always returned as nil [\#58](https://github.com/bbc/device_api-android/issues/58) 173 | - Expose reboot method from device [\#57](https://github.com/bbc/device_api-android/issues/57) 174 | - The 'battery\_info' method needs removing from device.rb [\#49](https://github.com/bbc/device_api-android/issues/49) 175 | - Add wifi status [\#30](https://github.com/bbc/device_api-android/issues/30) 176 | - Memory information needs adding [\#29](https://github.com/bbc/device_api-android/issues/29) 177 | - Device disk stats need adding [\#28](https://github.com/bbc/device_api-android/issues/28) 178 | - Typo in readme [\#17](https://github.com/bbc/device_api-android/issues/17) 179 | - Signing needs documentation [\#16](https://github.com/bbc/device_api-android/issues/16) 180 | - Readme & license need adding to the gemspec files [\#15](https://github.com/bbc/device_api-android/issues/15) 181 | - Public api methods need documentation [\#14](https://github.com/bbc/device_api-android/issues/14) 182 | - Update gemspec for open source [\#13](https://github.com/bbc/device_api-android/issues/13) 183 | - License file and copyright notice required [\#12](https://github.com/bbc/device_api-android/issues/12) 184 | 185 | **Merged pull requests:** 186 | 187 | - Added device uptime wrapper [\#64](https://github.com/bbc/device_api-android/pull/64) ([Asimk21](https://github.com/Asimk21)) 188 | - Added disk plugin [\#61](https://github.com/bbc/device_api-android/pull/61) ([Asimk21](https://github.com/Asimk21)) 189 | - Added a wrapper for reboot from device [\#60](https://github.com/bbc/device_api-android/pull/60) ([Asimk21](https://github.com/Asimk21)) 190 | - Fix for returning RAM info for all devices including kindle [\#59](https://github.com/bbc/device_api-android/pull/59) ([Asimk21](https://github.com/Asimk21)) 191 | - Improving code [\#54](https://github.com/bbc/device_api-android/pull/54) ([jonpwilson](https://github.com/jonpwilson)) 192 | - Memory plugin [\#53](https://github.com/bbc/device_api-android/pull/53) ([jonpwilson](https://github.com/jonpwilson)) 193 | - Added rspec for wifi and am [\#52](https://github.com/bbc/device_api-android/pull/52) ([Asimk21](https://github.com/Asimk21)) 194 | - Intent [\#48](https://github.com/bbc/device_api-android/pull/48) ([Asimk21](https://github.com/Asimk21)) 195 | - Try to reduce Complexity [\#47](https://github.com/bbc/device_api-android/pull/47) ([Asimk21](https://github.com/Asimk21)) 196 | - Refactor installer [\#46](https://github.com/bbc/device_api-android/pull/46) ([jonpwilson](https://github.com/jonpwilson)) 197 | - Battery info [\#45](https://github.com/bbc/device_api-android/pull/45) ([Asimk21](https://github.com/Asimk21)) 198 | - Removed info and apps accessors from memory as they are no longer req… [\#44](https://github.com/bbc/device_api-android/pull/44) ([jonpwilson](https://github.com/jonpwilson)) 199 | - Moved 'wifi\_status' to public [\#43](https://github.com/bbc/device_api-android/pull/43) ([Asimk21](https://github.com/Asimk21)) 200 | - Device type - based on the DPI of the attached device [\#42](https://github.com/bbc/device_api-android/pull/42) ([jonpwilson](https://github.com/jonpwilson)) 201 | - Am [\#40](https://github.com/bbc/device_api-android/pull/40) ([Asimk21](https://github.com/Asimk21)) 202 | - Updated Gemfile.lock to use latest device\_api [\#38](https://github.com/bbc/device_api-android/pull/38) ([jonpwilson](https://github.com/jonpwilson)) 203 | - Memory info [\#37](https://github.com/bbc/device_api-android/pull/37) ([jonpwilson](https://github.com/jonpwilson)) 204 | - Test fixing [\#36](https://github.com/bbc/device_api-android/pull/36) ([jonpwilson](https://github.com/jonpwilson)) 205 | - Updated 'result' type [\#35](https://github.com/bbc/device_api-android/pull/35) ([Asimk21](https://github.com/Asimk21)) 206 | - Getprop fix [\#34](https://github.com/bbc/device_api-android/pull/34) ([jonpwilson](https://github.com/jonpwilson)) 207 | - Re-added recent Wifi changes [\#33](https://github.com/bbc/device_api-android/pull/33) ([jonpwilson](https://github.com/jonpwilson)) 208 | - Removed devices folder [\#32](https://github.com/bbc/device_api-android/pull/32) ([jonpwilson](https://github.com/jonpwilson)) 209 | - Added wifi status [\#31](https://github.com/bbc/device_api-android/pull/31) ([Asimk21](https://github.com/Asimk21)) 210 | - Small ADB.rb refactor and added battery info methods [\#25](https://github.com/bbc/device_api-android/pull/25) ([jonpwilson](https://github.com/jonpwilson)) 211 | - Changed device registration so that a Kindle specific object is created [\#24](https://github.com/bbc/device_api-android/pull/24) ([jonpwilson](https://github.com/jonpwilson)) 212 | - Update device.rb [\#23](https://github.com/bbc/device_api-android/pull/23) ([jitgo](https://github.com/jitgo)) 213 | - Fix test failures [\#21](https://github.com/bbc/device_api-android/pull/21) ([davidbuckhurst](https://github.com/davidbuckhurst)) 214 | - Updated README to include signing information [\#20](https://github.com/bbc/device_api-android/pull/20) ([jonpwilson](https://github.com/jonpwilson)) 215 | - Added yardoc comments - yardoc coverage is at 100% [\#19](https://github.com/bbc/device_api-android/pull/19) ([jonpwilson](https://github.com/jonpwilson)) 216 | - License [\#18](https://github.com/bbc/device_api-android/pull/18) ([davidbuckhurst](https://github.com/davidbuckhurst)) 217 | - Trap an error caused by some shells when a binary isn't found [\#11](https://github.com/bbc/device_api-android/pull/11) ([jonpwilson](https://github.com/jonpwilson)) 218 | - Corrected the keystore algorithm [\#10](https://github.com/bbc/device_api-android/pull/10) ([jonpwilson](https://github.com/jonpwilson)) 219 | - Added a check to see if an APK is signed before attempting to sign it [\#9](https://github.com/bbc/device_api-android/pull/9) ([jonpwilson](https://github.com/jonpwilson)) 220 | - Added simple range logic and device property [\#8](https://github.com/bbc/device_api-android/pull/8) ([jonpwilson](https://github.com/jonpwilson)) 221 | - Added keystore generation and APK signing [\#7](https://github.com/bbc/device_api-android/pull/7) ([jonpwilson](https://github.com/jonpwilson)) 222 | - Removed the iphonesubinfo command [\#6](https://github.com/bbc/device_api-android/pull/6) ([jonpwilson](https://github.com/jonpwilson)) 223 | - IMEI Number wrapping [\#5](https://github.com/bbc/device_api-android/pull/5) ([jonpwilson](https://github.com/jonpwilson)) 224 | - capture screenshots [\#4](https://github.com/bbc/device_api-android/pull/4) ([davidbuckhurst](https://github.com/davidbuckhurst)) 225 | - Added in udev rules [\#3](https://github.com/bbc/device_api-android/pull/3) ([jonpwilson](https://github.com/jonpwilson)) 226 | 227 | ## [v1.0.0](https://github.com/bbc/device_api-android/tree/v1.0.0) (2014-09-05) 228 | **Merged pull requests:** 229 | 230 | - Recreate main class and strip out base gem tests [\#2](https://github.com/bbc/device_api-android/pull/2) ([davidbuckhurst](https://github.com/davidbuckhurst)) 231 | - First build [\#1](https://github.com/bbc/device_api-android/pull/1) ([wordswords](https://github.com/wordswords)) 232 | 233 | 234 | 235 | \* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'codeclimate-test-reporter', group: :test, require: nil 4 | 5 | gemspec 6 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | device_api-android (1.2.20) 5 | android-devices 6 | device_api (~> 1.0.2) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | android-devices (1.0.1) 12 | codeclimate-test-reporter (1.0.8) 13 | simplecov (<= 0.13) 14 | device_api (1.0.2) 15 | diff-lcs (1.3) 16 | docile (1.1.5) 17 | json (2.1.0) 18 | rspec (3.5.0) 19 | rspec-core (~> 3.5.0) 20 | rspec-expectations (~> 3.5.0) 21 | rspec-mocks (~> 3.5.0) 22 | rspec-core (3.5.4) 23 | rspec-support (~> 3.5.0) 24 | rspec-expectations (3.5.0) 25 | diff-lcs (>= 1.2.0, < 2.0) 26 | rspec-support (~> 3.5.0) 27 | rspec-mocks (3.5.0) 28 | diff-lcs (>= 1.2.0, < 2.0) 29 | rspec-support (~> 3.5.0) 30 | rspec-support (3.5.0) 31 | simplecov (0.13.0) 32 | docile (~> 1.1.0) 33 | json (>= 1.8, < 3) 34 | simplecov-html (~> 0.10.0) 35 | simplecov-html (0.10.0) 36 | 37 | PLATFORMS 38 | ruby 39 | 40 | DEPENDENCIES 41 | codeclimate-test-reporter 42 | device_api-android! 43 | rspec (~> 3) 44 | simplecov (~> 0.12) 45 | 46 | BUNDLED WITH 47 | 1.14.6 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeviceAPI-Android 2 | [![Build Status](https://travis-ci.org/bbc/device_api-android.svg?branch=master)](https://travis-ci.org/bbc/device_api-android) 3 | 4 | *DeviceAPI-Android* is the android implementation of device_api -- an initiative to allow full automation of device activities. For a full list of release notes, see the [change log](CHANGELOG.md) 5 | 6 | ## Dependencies 7 | 8 | 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: 9 | 10 | * adb 11 | * aapt 12 | 13 | ## Using the gem 14 | 15 | 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. 16 | 17 | gem 'device_api-android' 18 | 19 | You'll need to require the library in your code: 20 | 21 | require 'device_api/android' 22 | 23 | Try connecting an android device with usb, and run: 24 | 25 | devices = DeviceAPI::Android.devices 26 | 27 | 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. 28 | 29 | ### Connecting and disconnecting from Remote Devices 30 | 31 | You can connect to devices via their IP address and port number. The syntax is: 32 | 33 | DeviceAPI::Android.connect(,=5555) 34 | 35 | This should add a device to the already connected devices, which you can query with DeviceAPI::Android.devices. You can disconnect from a device like so: 36 | 37 | DeviceAPI::Android.disconnect(,=5555) 38 | 39 | Once connected, the IP address and port number combination becomes the serial for the device, and you can execute commands such as adb shell through specifying the IP address/port number instead of the serial number. For both Android.connect and Android.disconnect, if port number is not specified, and ip address is only specified, port number defaults to 5555. (Note that Android.disconnect doesn't automagically disconnect you from a connection with a port number that is not 5555 when it is called without a port argument) 40 | 41 | You can also use the disconnect method on a Android device object, without any arguments to disconnect a device. It will throw an error if the device is not connected. 42 | 43 | device.disconnect 44 | 45 | You can use device.is_remote? to determine if the device is a remote device, e.g. it has a ipaddress and port as an adb serial, and can attempt to be connected to. 46 | 47 | device.is_remote? 48 | 49 | ### Error messages 50 | 51 | Here are some of the errors you may encounter as well as a example of what can cause them: 52 | 53 | `DeviceAPI::Android::ADB::DeviceAlreadyConnectedError` - raised when DeviceAPI::Android.connect is called on an currently connected device. 54 | 55 | `DeviceAPI::Android::DeviceDisconnectedWhenNotARemoteDevice` - raised when we are attempting to call disconnect on a non-remote device. 56 | 57 | `DeviceAPI::Android::ADBCommandError` - raised when we cannot connect to a device, e.g. adb times out. 58 | 59 | ### Detecting devices 60 | 61 | There are two methods for detecting devices: 62 | 63 | DeviceAPI::Android.devices 64 | 65 | This returns an array of objects representing the connected devices. You get an empty array if there are no connected devices. 66 | 67 | DeviceAPI::Android.device(serial_id) 68 | 69 | This looks for a device with a matching serial_id and returns a single device object. 70 | 71 | ### Device object 72 | 73 | When device-api detects a device, it returns a device object that lets you interact with and query the device with various android tools. 74 | 75 | For example: 76 | 77 | device = DeviceAPI::Android.device(serial_id) 78 | device.serial # "01498A0004005015" 79 | device.model # "Galaxy Nexus" 80 | 81 | #### Device orientation 82 | 83 | device.orientation # :landscape / :portrait 84 | 85 | #### Install/uninstall apk 86 | 87 | device.install('location/apk_to_install.apk') # will install the apk on the device 88 | device.uninstall('my.package.name') # will uninstall the package matching the package name 89 | 90 | #### APK Signing 91 | 92 | An APK can be signed using *DeviceAPI*. To do so you can simply run: 93 | 94 | DeviceAPI::Android::Signing.sign_apk({apk: apk_path, resign: true}) 95 | 96 | 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: 97 | 98 | DeviceAPI::Android::Signing.generate_keystore( { keystore: '~/new_kestore.keystore', password: 'new_password' } ) 99 | 100 | This allows you to setup a keystore with the options required by any testing framework 101 | 102 | ### Package details 103 | 104 | device.package_name('app.apk') # returns some.package.name 105 | device.app_version_number('app.apk') # returns v#.#.# 106 | 107 | ## Testing 108 | 109 | device_api-android is defended with unit and integration level rspec tests. You can run the tests with: 110 | 111 | bundle exec rspec 112 | 113 | ## Issues 114 | 115 | If you plug in a device and adb shows the device as having no permissions as seen here: 116 | 117 | hive@hive-04:~$ adb devices 118 | List of devices attached 119 | ???????????? no permissions 120 | 121 | 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 122 | 123 | adb kill-server 124 | adb start-server 125 | 126 | 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: 127 | 128 | lsusb 129 | 130 | You should be presented with something similar to this: 131 | 132 | Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub 133 | Bus 001 Device 020: ID 0e79:5009 Archos, Inc. 134 | Bus 001 Device 003: ID 05ac:8242 Apple, Inc. Built-in IR Receiver 135 | Bus 001 Device 006: ID 05ac:8289 Apple, Inc. 136 | Bus 001 Device 002: ID 0a5c:4500 Broadcom Corp. BCM2046B1 USB 2.0 Hub (part of BCM2046 Bluetooth) 137 | Bus 001 Device 011: ID 05c6:6765 Qualcomm, Inc. 138 | Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub 139 | 140 | 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 combined 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: 141 | 142 | SUBSYSTEM=="usb", ATTR{idVendor}=="0e79", ATTR{idProduct}=="5009", MODE="0666", OWNER="hive" 143 | 144 | 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. 145 | 146 | ## License 147 | 148 | *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. 149 | 150 | Copyright (c) 2016 BBC 151 | -------------------------------------------------------------------------------- /device_api-android.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'device_api-android' 3 | s.version = '1.2.20' 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 = ['David Buckhurst','Jitesh Gosai', 'Asim Khan', 'Jon Wilson'] 8 | s.email = 'david.buckhurst@bbc.co.uk' 9 | s.files = Dir['README.md', 'lib/**/*.rb'] 10 | s.homepage = 'https://github.com/bbc/device_api-android' 11 | s.license = 'MIT' 12 | s.add_runtime_dependency 'device_api', '~> 1.0.2' 13 | s.add_development_dependency 'rspec', '~> 3' 14 | s.add_development_dependency 'simplecov', '~> 0.12' 15 | s.add_runtime_dependency 'android-devices' 16 | end 17 | -------------------------------------------------------------------------------- /lib/device_api/android.rb: -------------------------------------------------------------------------------- 1 | # Encoding: utf-8 2 | require 'device_api/android/adb' 3 | require 'device_api/android/device' 4 | require 'device_api/android/signing' 5 | 6 | # Load plugins 7 | require 'device_api/android/plugins/audio' 8 | require 'device_api/android/plugins/memory' 9 | require 'device_api/android/plugins/battery' 10 | require 'device_api/android/plugins/disk' 11 | 12 | # Load additional device types 13 | require 'device_api/android/device/kindle' 14 | require 'device_api/android/device/samsung' 15 | 16 | module DeviceAPI 17 | module Android 18 | # Returns array of connected android devices 19 | def self.devices 20 | ADB.devices.map do |d| 21 | if d.keys.first && !d.keys.first.include?('?') 22 | qualifier = d.keys.first 23 | remote = check_if_remote_device?(qualifier) 24 | DeviceAPI::Android::Device.create( self.get_device_type(d), { qualifier: qualifier, state: d.values.first, remote: remote} ) 25 | end 26 | end.compact 27 | end 28 | 29 | # Retrieve an Device object by serial id 30 | def self.device(qualifier) 31 | if qualifier.to_s.empty? 32 | raise DeviceAPI::BadSerialString.new("Qualifier was '#{qualifier.nil? ? 'nil' : qualifier}'") 33 | end 34 | device = ADB.devices.select {|k| k.keys.first == qualifier} 35 | state = device.first[qualifier] || 'unknown' 36 | remote = check_if_remote_device?(qualifier) 37 | DeviceAPI::Android::Device.create( self.get_device_type({ :"#{qualifier}" => state}), { qualifier: qualifier, state: state, remote: remote }) 38 | end 39 | 40 | def self.connect(ipaddress,port=5555) 41 | ADB.connect(ipaddress,port) 42 | end 43 | 44 | def self.disconnect(ipaddress,port=5555) 45 | ADB.disconnect(ipaddress,port) 46 | end 47 | 48 | def self.check_if_remote_device?(qualifier) 49 | begin 50 | ADB::check_ip_address(qualifier) 51 | true 52 | rescue ADBCommandError 53 | false 54 | end 55 | end 56 | 57 | # Return the device type used in determining which Device Object to create 58 | def self.get_device_type(options) 59 | return :default if ['unauthorized', 'offline', 'unknown'].include? options.values.first 60 | qualifier = options.keys.first 61 | state = options.values.first 62 | begin 63 | man = Device.new(qualifier: qualifier, state: state).manufacturer 64 | rescue DeviceAPI::DeviceNotFound 65 | return :default 66 | rescue => e 67 | puts "Unrecognised exception whilst finding device '#{qualifier}' (state: #{state})" 68 | puts e.message 69 | puts e.backtrace.inspect 70 | return :default 71 | end 72 | return :default if man.nil? 73 | case man.downcase 74 | when 'amazon' 75 | type = :kindle 76 | when 'samsung' 77 | type = :samsung 78 | else 79 | type = :default 80 | end 81 | type 82 | end 83 | 84 | # Serial error class 85 | class BadSerialString < StandardError 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/device_api/android/aapt.rb: -------------------------------------------------------------------------------- 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 | result.stdout.scan(/(.*): (.*)/).map { |a,b| { a => Hash[b.split(' ').map { |c| c.tr('\'','').split('=') }] } } 30 | end 31 | 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/device_api/android/adb.rb: -------------------------------------------------------------------------------- 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 | result.stdout.scan(/(.*)\t(.*)/).map { |a,b| {a => b}} 22 | end 23 | 24 | # Retrieve device state for a single device 25 | # @param qualifier qualifier of device 26 | # @return (String) device state 27 | def self.get_state(qualifier) 28 | result = execute('adb -s #{qualifier} get-state') 29 | 30 | raise ADBCommandError.new(result.stderr) if result.exit != 0 31 | 32 | lines = result.stdout.split("\n") 33 | /(.*)/.match(lines.last) 34 | Regexp.last_match[0].strip 35 | end 36 | 37 | # Get the properties of a specified device 38 | # @param qualifier qualifier of device 39 | # @return (Hash) hash containing device properties 40 | def self.getprop(qualifier) 41 | result = shell(qualifier, 'getprop') 42 | 43 | lines = result.stdout.encode('UTF-16', 'UTF-8', invalid: :replace, replace: '').encode('UTF-8', 'UTF-16').split("\n") 44 | 45 | process_dumpsys('\[(.*)\]:\s+\[(.*)\]', lines) 46 | end 47 | 48 | # Get the 'input' information from dumpsys 49 | # @param qualifier qualifier of device 50 | # @return (Hash) hash containing input information from dumpsys 51 | def self.getdumpsys(qualifier) 52 | lines = dumpsys(qualifier, 'input') 53 | process_dumpsys('(.*):\s+(.*)', lines) 54 | end 55 | 56 | # Get the 'iphonesubinfo' from dumpsys 57 | # @param qualifier qualifier of device 58 | # @return (Hash) hash containing iphonesubinfo information from dumpsys 59 | def self.getphoneinfo(qualifier) 60 | lines = dumpsys(qualifier, 'iphonesubinfo') 61 | process_dumpsys('(.*) =\s+(.*)', lines) 62 | end 63 | 64 | # Get the 'battery' information from dumpsys 65 | # @param [String] qualifier qualifier of device 66 | # @return [Hash] hash containing battery information from dumpsys 67 | def self.get_battery_info(qualifier) 68 | lines = dumpsys(qualifier, 'battery') 69 | process_dumpsys('(.*):\s+(.*)', lines) 70 | end 71 | 72 | def self.get_network_interface(qualifier, interface) 73 | result = shell(qualifier, "ifconfig #{interface}") 74 | result.stdout 75 | end 76 | 77 | # Get the network information 78 | def self.get_network_info(qualifier) 79 | lines = shell(qualifier, 'netcfg') 80 | lines.stdout.split("\n").map do |a| 81 | b = a.split(" ") 82 | { name: b[0], ip: b[2].split('/')[0], mac: b[4] } 83 | end 84 | end 85 | 86 | def self.get_wifi_mac_address(qualifier) 87 | lines = shell(qualifier, 'ip address') 88 | lines = lines.to_s.gsub(/\r\n/, '') if lines 89 | match_data = lines.match(/wlan0: .+? (\w{2}:\w{2}:\w{2}:\w{2}:\w{2}:\w{2})/) 90 | match_data[1] if match_data 91 | end 92 | 93 | # Processes the results from dumpsys to format them into a hash 94 | # @param [String] regex_string regex string used to separate the results from the keys 95 | # @param [Array] data data returned from dumpsys 96 | # @return [Hash] hash containing the keys and values as distinguished by the supplied regex 97 | def self.process_dumpsys(regex_string, data) 98 | props = {} 99 | regex = Regexp.new(regex_string) 100 | data.each do |line| 101 | if regex.match(line) 102 | props[Regexp.last_match[1]] = Regexp.last_match[2] 103 | end 104 | end 105 | 106 | props 107 | end 108 | 109 | # Get the 'power' information from dumpsys 110 | # @param [String] qualifier qualifier of device 111 | # @return [Hash] hash containing power information from dumpsys 112 | def self.getpowerinfo(qualifier) 113 | lines = dumpsys(qualifier, 'power') 114 | process_dumpsys('(.*)=(.*)', lines) 115 | end 116 | 117 | def self.get_device_dpi(qualifier) 118 | lines = dumpsys(qualifier, 'window') 119 | dpi = nil 120 | lines.each do |line| 121 | if /sw(\d*)dp/.match(line) 122 | dpi = Regexp.last_match[1] 123 | end 124 | end 125 | dpi 126 | end 127 | 128 | # Returns the 'dumpsys' information from the specified device 129 | # @param qualifier qualifier of device 130 | # @return (Array) array of results from adb shell dumpsys 131 | def self.dumpsys(qualifier, command) 132 | result = shell(qualifier, "dumpsys #{command}") 133 | result.stdout.split("\n").map { |line| line.strip } 134 | end 135 | 136 | # Installs a specified apk to a specific device 137 | # @param [Hash] options the options used for installing an apk 138 | # @option options [String] :apk path to apk to install 139 | # @option options [String] :qualifier qualifier of device 140 | # @return (String) return result from adb install command 141 | def self.install_apk(options = {}) 142 | options[:action] = :install 143 | change_apk(options) 144 | end 145 | 146 | # Uninstalls a specified package from a specified device 147 | # @param [Hash] options the options used for uninstalling a package 148 | # @option options [String] :package_name package to uninstall 149 | # @option options [String] :qualifier qualifier of device 150 | # @return (String) return result from adb uninstall command 151 | def self.uninstall_apk(options = {}) 152 | options[:action] = :uninstall 153 | change_apk(options) 154 | end 155 | 156 | def self.change_apk(options = {}) 157 | package_name = options[:package_name] 158 | apk = options[:apk] 159 | qualifier = options[:qualifier] 160 | action = options[:action] 161 | 162 | case action 163 | when :install 164 | command = "adb -s #{qualifier} install #{apk}" 165 | when :uninstall 166 | command = "adb -s #{qualifier} uninstall #{package_name}" 167 | else 168 | raise ADBCommandError.new('No action specified') 169 | end 170 | 171 | result = execute(command) 172 | 173 | raise ADBCommandError.new(result.stderr) if result.exit != 0 174 | 175 | lines = result.stdout.split("\n").map { |line| line.strip } 176 | 177 | lines.last 178 | end 179 | 180 | # Returns the uptime of the specified device 181 | # @param qualifier qualifier of device 182 | # @return (Float) uptime in seconds 183 | def self.get_uptime(qualifier) 184 | result = shell(qualifier, 'cat /proc/uptime') 185 | 186 | lines = result.stdout.split("\n") 187 | uptime = 0 188 | lines.each do |l| 189 | if /([\d.]*)\s+[\d.]*/.match(l) 190 | uptime = Regexp.last_match[0].to_f.round 191 | end 192 | end 193 | uptime 194 | end 195 | 196 | # Reboots the specified device 197 | # Remote devices are rebooted and disconnected from system 198 | # @param qualifier qualifier of device 199 | # @return (nil) Nil if successful, otherwise an error is raised 200 | def self.reboot(qualifier, remote) 201 | if remote 202 | begin 203 | system("adb -s #{qualifier} reboot &") 204 | self.disconnect(qualifier.split(":").first) 205 | rescue => e 206 | raise ADBCommandError.new(e) 207 | end 208 | else 209 | result = execute("adb -s #{qualifier} reboot && adb -s #{qualifier} wait-for-device shell 'while [[ $(getprop dev.bootcomplete | tr -d '\r') != 1 ] ]; do sleep 1; printf .; done'") 210 | raise ADBCommandError.new(result.stderr) if result.exit != 0 211 | end 212 | end 213 | 214 | # Runs monkey testing 215 | # @param qualifier qualifier of device 216 | # @param [Hash] args hash of arguments used for starting testing 217 | # @option args [String] :events (10000) number of events to run 218 | # @option args [String] :package name of package to run the tests against 219 | # @option args [String] :seed pass the seed number (optional) 220 | # @option args [String] :throttle throttle value (optional) 221 | # @example 222 | # DeviceAPI::ADB.monkey( qualifier, :package => 'my.lovely.app' ) 223 | def self.monkey(qualifier, args) 224 | 225 | events = args[:events] || 10000 226 | package = args[:package] or raise "package name not provided (:package => 'bbc.iplayer')" 227 | seed = args[:seed] 228 | throttle = args[:throttle] 229 | 230 | cmd = "monkey -p #{package} -v #{events}" 231 | cmd = cmd + " -s #{seed}" if seed 232 | cmd = cmd + " -t #{throttle}" if throttle 233 | 234 | shell(qualifier, cmd) 235 | end 236 | 237 | # Take a screenshot from the device 238 | # @param qualifier qualifier of device 239 | # @param [Hash] args hash of arguments 240 | # @option args [String] :filename name (with full path) required to save the image 241 | # @example 242 | # DeviceAPI::ADB.screenshot( qualifier, :filename => '/tmp/filename.png' ) 243 | def self.screencap( qualifier, args ) 244 | 245 | filename = args[:filename] or raise "filename not provided (:filename => '/tmp/myfile.png')" 246 | 247 | if getprop(qualifier)['ro.build.version.release'].to_i < 7 248 | convert_carriage_returns = %q{perl -pe 's/\x0D\x0A/\x0A/g'} 249 | cmd = "screencap -p | #{convert_carriage_returns} > #{filename}" 250 | else 251 | cmd = "screencap -p > #{filename}" 252 | end 253 | 254 | shell(qualifier, cmd) 255 | end 256 | 257 | # Connects to remote android device 258 | # @param [String] ip_address 259 | # @param [String] port 260 | # @example 261 | # DeviceAPI::ADB.connect(ip_address, port) 262 | def self.connect(ip_address, port=5555) 263 | ip_address_and_port = "#{ip_address}:#{port}" 264 | check_ip_address(ip_address_and_port) 265 | cmd = "adb connect #{ip_address_and_port}" 266 | result = execute(cmd) 267 | if result.stdout.to_s =~ /.*already connected to.*/ 268 | raise DeviceAlreadyConnectedError.new("Device #{ip_address_and_port} already connected") 269 | else 270 | unless result.stdout.to_s =~ /.*connected to.*/ 271 | raise ADBCommandError.new("Unable to adb connect to #{ip_address_and_port} result was: #{result.stdout}") 272 | end 273 | end 274 | end 275 | 276 | # Disconnects from remote android device 277 | # @param [String] ip_address 278 | # @param [String] port 279 | # @example 280 | # DeviceAPI::ADB.disconnect(ip_address, port) 281 | def self.disconnect(ip_address, port=5555) 282 | ip_address_and_port = "#{ip_address}:#{port}" 283 | check_ip_address(ip_address_and_port) 284 | cmd = "adb disconnect #{ip_address_and_port}" 285 | result = execute(cmd) 286 | unless result.exit == 0 287 | raise ADBCommandError.new("Unable to adb disconnect to #{ip_address_and_port} result was: #{result.stdout}") 288 | end 289 | end 290 | 291 | # Returns wifi status and access point name 292 | # @param qualifier qualifier of device 293 | # @example 294 | # DeviceAPI::ADB.wifi(qualifier) 295 | def self.wifi(qualifier) 296 | result = shell(qualifier, 'dumpsys wifi | grep mNetworkInfo') 297 | 298 | {:status => result.stdout.match("state:(.*?),")[1].strip, :access_point => result.stdout.match("extra:(.*?),")[1].strip.gsub(/"/,'')} 299 | end 300 | 301 | # Sends a key event to the specified device 302 | # @param [String] qualifier qualifier of device 303 | # @param [String] keyevent keyevent to send to the device 304 | def self.keyevent(qualifier, keyevent) 305 | shell(qualifier, "input keyevent #{keyevent}").stdout 306 | end 307 | 308 | # ADB Shell command 309 | # @param [String] qualifier qualifier of device 310 | # @param [String] command command to execute 311 | def self.shell(qualifier, command) 312 | result = execute("adb -s '#{qualifier}' shell #{command}") 313 | case result.stderr 314 | when /^error: device unauthorized./ 315 | raise DeviceAPI::UnauthorizedDevice, result.stderr 316 | when /^error: device not found/ 317 | raise DeviceAPI::DeviceNotFound, result.stderr 318 | # ADB.get_network_info on android > 7 behave differently 319 | # On linux exit code is 127 320 | # On MAC exit code is 0 321 | # Caught here to give get_network_info consistent response 322 | when /^\/system\/bin\/sh: netcfg: not found/ 323 | return result 324 | else 325 | raise ADBCommandError.new(result.stderr) 326 | end if result.exit != 0 327 | 328 | result 329 | end 330 | 331 | # Sends a swipe command to the specified device 332 | # @param [String] qualifier qualifier of the device 333 | # @param [Hash] coords hash of coordinates to swipe from / to 334 | # @option coords [String] :x_from (0) Coordinate to start from on the X axis 335 | # @option coords [String] :x_to (0) Coordinate to end on on the X axis 336 | # @option coords [String] :y_from (0) Coordinate to start from on the Y axis 337 | # @option coords [String] :y_to (0) Coordinate to end on on the Y axis 338 | def self.swipe(qualifier, coords = {x_from: 0, y_from: 0, x_to: 0, y_to: 0 }) 339 | shell(qualifier, "input swipe #{coords[:x_from]} #{coords[:y_from]} #{coords[:x_to]} #{coords[:y_to]}").stdout 340 | end 341 | 342 | # Starts intent using adb 343 | # Returns stdout 344 | # @param qualifier qualifier of device 345 | # @param command -option activity 346 | # @example 347 | # DeviceAPI::ADB.am(qualifier, "start -a android.intent.action.MAIN -n com.android.settings/.wifi.WifiSettings") 348 | def self.am(qualifier, command) 349 | shell(qualifier, "am #{command}").stdout 350 | end 351 | 352 | # Package manager commands 353 | # @param qualifier qualifier of device 354 | # @param command command to issue to the package manager 355 | # @example DeviceAPI::ADB.pm(qualifier, 'list packages') 356 | def self.pm(qualifier, command) 357 | shell(qualifier, "pm #{command}").stdout 358 | end 359 | 360 | # Blocks a package, used on Android versions less than KitKat 361 | # Returns boolean 362 | # @param qualifier qualifier of device 363 | # @param package to block 364 | def self.block_package(qualifier, package) 365 | result = pm(qualifier, "block #{package}") 366 | result.include?('true') 367 | end 368 | 369 | # Blocks a package on KitKat and above 370 | # Returns boolean 371 | # @param qualifier qualifier of device 372 | # @param package to hide 373 | def self.hide_package(qualifier, package) 374 | result = pm(qualifier, "hide #{package}") 375 | result.include?('true') 376 | end 377 | 378 | def self.check_ip_address(ip_address_and_port) 379 | unless ip_address_and_port =~ /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3}):[0-9]+\Z/ 380 | raise ADBCommandError.new("Invalid IP address and port #{ip_address_and_port}") 381 | end 382 | end 383 | end 384 | 385 | # ADB Error class 386 | class ADBCommandError < StandardError 387 | def initialize(msg) 388 | super(msg) 389 | end 390 | end 391 | class DeviceAlreadyConnectedError < ADBCommandError 392 | def initialize(msg) 393 | super(msg) 394 | end 395 | end 396 | 397 | end 398 | end 399 | -------------------------------------------------------------------------------- /lib/device_api/android/device.rb: -------------------------------------------------------------------------------- 1 | # Encoding: utf-8 2 | require 'device_api/device' 3 | require 'device_api/android/adb' 4 | require 'device_api/android/aapt' 5 | require 'android/devices' 6 | 7 | # DeviceAPI - an interface to allow for automation of devices 8 | module DeviceAPI 9 | # Android component of DeviceAPI 10 | module Android 11 | # Device class used for containing the accessors of the physical device information 12 | class Device < DeviceAPI::Device 13 | attr_reader :qualifier 14 | 15 | @@subclasses = {} 16 | 17 | # Called by any inheritors to register themselves with the parent class 18 | def self.inherited(klass) 19 | key = /::([^:]+)$/.match(klass.to_s.downcase)[1].to_sym 20 | @@subclasses[key] = klass 21 | end 22 | 23 | # Returns an object of the specified type, if it exists. Defaults to returning self 24 | def self.create(type, options = {} ) 25 | return @@subclasses[type.to_sym].new(options) if @@subclasses[type.to_sym] 26 | return self.new(options) 27 | end 28 | 29 | def initialize(options = {}) 30 | # For devices connected with USB, qualifier and serial are same 31 | @qualifier = options[:qualifier] 32 | @state = options[:state] 33 | @serial = options[:serial] || @qualifier 34 | @remote = options[:remote] ? true : false 35 | if is_remote? 36 | set_ip_and_port 37 | @serial = self.serial_no if !["unknown", "offline"].include? @state 38 | end 39 | end 40 | 41 | def set_ip_and_port 42 | address = @qualifier.split(":") 43 | @ip_address = address.first 44 | @port = address.last 45 | end 46 | 47 | def is_remote? 48 | @remote || false 49 | end 50 | 51 | # Mapping of device status - used to provide a consistent status across platforms 52 | # @return (String) common status string 53 | def status 54 | { 55 | 'device' => :ok, 56 | 'no device' => :dead, 57 | 'offline' => :offline, 58 | 'unauthorized' => :unauthorized, 59 | 'no permissions' => :no_permissions, 60 | 'unknown' => :unknown 61 | }[@state] 62 | end 63 | 64 | def connect 65 | ADB.connect(@ip_address, @port) 66 | end 67 | 68 | def disconnect 69 | unless is_remote? 70 | raise DeviceAPI::Android::DeviceDisconnectedWhenNotARemoteDevice.new("Asked to disconnect device #{qualifier} when it is not a remote device") 71 | end 72 | ADB.disconnect(@ip_address, @port) 73 | end 74 | 75 | # Return whether device is connected or not 76 | def is_connected? 77 | ADB.devices.any? {|device| device.include? qualifier} 78 | end 79 | 80 | def display_name 81 | device = Android::Devices.search_by_model(model) 82 | device.model unless device.nil? 83 | end 84 | 85 | # Return the device range 86 | # @return (String) device range string 87 | def range 88 | device = self.device 89 | model = self.model 90 | 91 | return device if device == model 92 | "#{device}_#{model}" 93 | end 94 | 95 | # Return the serial number of device 96 | # @return (String) serial number 97 | def serial_no 98 | get_prop('ro.serialno') 99 | end 100 | 101 | # Return the device type 102 | # @return (String) device type string 103 | def device 104 | get_prop('ro.product.device') 105 | end 106 | 107 | # Return the device model 108 | # @return (String) device model string 109 | def model 110 | get_prop('ro.product.model') 111 | end 112 | 113 | # Return the device manufacturer 114 | # @return (String) device manufacturer string 115 | def manufacturer 116 | get_prop('ro.product.manufacturer') 117 | end 118 | 119 | # Return the Android OS version 120 | # @return (String) device Android version 121 | def version 122 | get_prop('ro.build.version.release') 123 | end 124 | 125 | # Return the battery level 126 | # @return (String) device battery level 127 | def battery_level 128 | get_battery_info.level 129 | end 130 | 131 | # Is the device currently being powered? 132 | # @return (Boolean) true if it is being powered in some way, false if it is unpowered 133 | def powered? 134 | get_battery_info.powered 135 | end 136 | 137 | def block_package(package) 138 | if version < "5.0.0" 139 | ADB.block_package(qualifier, package) 140 | else 141 | ADB.hide_package(qualifier, package) 142 | end 143 | end 144 | 145 | # Return the device orientation 146 | # @return (String) current device orientation 147 | def orientation 148 | res = get_dumpsys('SurfaceOrientation') 149 | 150 | case res 151 | when '0','2' 152 | :portrait 153 | when '1', '3' 154 | :landscape 155 | when nil 156 | fail StandardError, 'No output returned is there a device connected?', caller 157 | else 158 | fail StandardError, "Device orientation not returned got: #{res}.", caller 159 | end 160 | end 161 | 162 | # Install a specified apk 163 | # @param [String] apk string containing path to the apk to install 164 | # @return [Symbol, Exception] :success when the apk installed successfully, otherwise an error is raised 165 | def install(apk) 166 | fail StandardError, 'No apk specified.', caller if apk.empty? 167 | res = install_apk(apk) 168 | 169 | case res 170 | when 'Success' 171 | :success 172 | else 173 | fail StandardError, res, caller 174 | end 175 | end 176 | 177 | # Uninstall a specified package 178 | # @param [String] package_name name of the package to uninstall 179 | # @return [Symbol, Exception] :success when the package is removed, otherwise an error is raised 180 | def uninstall(package_name) 181 | res = uninstall_apk(package_name) 182 | case res 183 | when 'Success' 184 | :success 185 | else 186 | fail StandardError, "Unable to install 'package_name' Error Reported: #{res}", caller 187 | end 188 | end 189 | 190 | # Return the package name for a specified apk 191 | # @param [String] apk string containing path to the apk 192 | # @return [String, Exception] package name if it can be found, otherwise an error is raised 193 | def package_name(apk) 194 | @apk = apk 195 | result = get_app_props('package')['name'] 196 | fail StandardError, 'Package name not found', caller if result.nil? 197 | result 198 | end 199 | 200 | def list_installed_packages 201 | packages = ADB.pm(qualifier, 'list packages') 202 | packages.split("\r\n") 203 | end 204 | 205 | # Return the app version number for a specified apk 206 | # @param [String] apk string containing path to the apk 207 | # @return [String, Exception] app version number if it can be found, otherwise an error is raised 208 | def app_version_number(apk) 209 | @apk = apk 210 | result = get_app_props('package')['versionName'] 211 | fail StandardError, 'Version number not found', caller if result.nil? 212 | result 213 | end 214 | 215 | # Initiate monkey tests 216 | # @param [Hash] args arguments to pass on to ADB.monkey 217 | def monkey(args) 218 | ADB.monkey(qualifier, args) 219 | end 220 | 221 | # Capture screenshot on device 222 | # @param [Hash] args arguments to pass on to ADB.screencap 223 | def screenshot(args) 224 | ADB.screencap(qualifier, args) 225 | end 226 | 227 | # Get the IMEI number of the device 228 | # @return (String) IMEI number of current device 229 | def imei 230 | get_phoneinfo['Device ID'] 231 | end 232 | 233 | # Get the memory information for the current device 234 | # @return [DeviceAPI::Android::Plugins::Memory] the memory plugin containing relevant information 235 | def memory 236 | get_memory_info 237 | end 238 | 239 | def battery 240 | get_battery_info 241 | end 242 | 243 | # Check if the devices screen is currently turned on 244 | # @return [Boolean] true if the screen is on, otherwise false 245 | def screen_on? 246 | power = get_powerinfo 247 | return true if power['mScreenOn'].to_s.downcase == 'true' || power['Display Power: state'].to_s.downcase == 'on' 248 | false 249 | end 250 | 251 | # Unlock the device by sending a wakeup command 252 | def unlock 253 | ADB.keyevent(qualifier, '26') unless screen_on? 254 | end 255 | 256 | # Return the DPI of the attached device 257 | # @return [String] DPI of attached device 258 | def dpi 259 | get_dpi(qualifier) 260 | end 261 | 262 | # Return the device type based on the DPI 263 | # @return [Symbol] :tablet or :mobile based upon the devices DPI 264 | def type 265 | if get_dpi.to_i > 533 266 | :tablet 267 | else 268 | :mobile 269 | end 270 | end 271 | 272 | # Returns wifi status and access point name 273 | # @return [Hash] :status and :access_point 274 | def wifi_status 275 | ADB.wifi(qualifier) 276 | end 277 | 278 | def battery_info 279 | ADB.get_battery_info(qualifier) 280 | end 281 | 282 | # @param [String] command to start the intent 283 | # Return the stdout of executed intent 284 | # @return [String] stdout 285 | def intent(command) 286 | ADB.am(qualifier, command) 287 | end 288 | 289 | #Reboots the device 290 | def reboot 291 | ADB.reboot(qualifier, is_remote?) 292 | end 293 | 294 | # Returns disk status 295 | # @return [Hash] containing disk statistics 296 | def diskstat 297 | get_disk_info 298 | end 299 | 300 | # Returns the device uptime 301 | def uptime 302 | ADB.get_uptime(qualifier) 303 | end 304 | 305 | # Returns the Wifi IP address 306 | def ip_address 307 | interface = ADB.get_network_interface(qualifier, 'wlan0') 308 | if interface.match(/ip (.*) mask/) 309 | Regexp.last_match[1] 310 | elsif interface.match(/inet addr:(.*)\s+Bcast/) 311 | Regexp.last_match[1].strip 312 | else 313 | # No match, wifi down? 314 | end 315 | end 316 | 317 | # Returns the Wifi mac address 318 | def wifi_mac_address 319 | return ADB.get_wifi_mac_address(qualifier) 320 | end 321 | 322 | def resolution 323 | res = ADB.dumpsys(qualifier, 'window | grep mUnrestrictedScreen') 324 | /^.* (.*)x(.*)$/.match(res.first) 325 | end 326 | 327 | private 328 | 329 | def get_network_info 330 | ADB.get_network_info(qualifier) 331 | end 332 | 333 | def get_disk_info 334 | @diskstat = DeviceAPI::Android::Plugin::Disk.new(qualifier: qualifier) unless @diskstat 335 | @diskstat.process_stats 336 | end 337 | 338 | def get_battery_info 339 | @battery = DeviceAPI::Android::Plugin::Battery.new(qualifier: qualifier) unless @battery 340 | @battery 341 | end 342 | 343 | def get_memory_info 344 | @memory = DeviceAPI::Android::Plugin::Memory.new(qualifier: qualifier) unless @memory 345 | @memory 346 | end 347 | 348 | def get_app_props(key) 349 | unless @app_props 350 | @app_props = AAPT.get_app_props(@apk) 351 | end 352 | @app_props.each { |x| break x[key] } 353 | end 354 | 355 | def get_prop(key) 356 | if !@props || !@props[key] 357 | @props = ADB.getprop(qualifier) 358 | end 359 | @props[key] 360 | end 361 | 362 | def get_dumpsys(key) 363 | @props = ADB.getdumpsys(qualifier) 364 | @props[key] 365 | end 366 | 367 | def get_powerinfo 368 | ADB.getpowerinfo(qualifier) 369 | end 370 | 371 | def get_phoneinfo 372 | ADB.getphoneinfo(qualifier) 373 | end 374 | 375 | def install_apk(apk) 376 | ADB.install_apk(apk: apk, qualifier: qualifier) 377 | end 378 | 379 | def uninstall_apk(package_name) 380 | ADB.uninstall_apk(package_name: package_name, qualifier: qualifier) 381 | end 382 | 383 | def get_dpi 384 | ADB.get_device_dpi(qualifier) 385 | end 386 | end 387 | 388 | class DeviceDisconnectedWhenNotARemoteDevice < StandardError 389 | def initialize(msg) 390 | super(msg) 391 | end 392 | end 393 | 394 | end 395 | end 396 | -------------------------------------------------------------------------------- /lib/device_api/android/device/kindle.rb: -------------------------------------------------------------------------------- 1 | module DeviceAPI 2 | module Android 3 | # Kindle specific device class 4 | class Kindle < Device 5 | # On non-Kindle devices, if a device is locked without a password (i.e. 'Swipe to unlock'), then 6 | # you can unlock that device by broadcasting a 'WakeUp' intent. On Kindle devices, this does not 7 | # work due to Amazons implementation of the Keyguard. 8 | def unlock 9 | ADB.keyevent(qualifier, '26') unless screen_on? 10 | ADB.swipe(qualifier, swipe_coords) 11 | end 12 | 13 | def swipe_coords 14 | res = resolution 15 | x = res[1].to_i 16 | y = res[2].to_i 17 | if version.split('.').first.to_i < 5 18 | {x_from: x - 100, y_from: y/2, x_to: x/6, y_to: y/2} 19 | else 20 | { x_from: x/2, y_from: y -100, x_to: x/2, y_to: y/6} 21 | end 22 | end 23 | 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/device_api/android/device/samsung.rb: -------------------------------------------------------------------------------- 1 | module DeviceAPI 2 | module Android 3 | # Samsung specific device class 4 | class Samsung < Device 5 | def initialize(options = {}) 6 | 7 | super 8 | packages = list_installed_packages 9 | multi_window = 'com.sec.android.app.FlashBarService' 10 | if packages.include?("package:#{multi_window}") 11 | # Stop the multi window function from running and block it 12 | intent("force-stop #{multi_window}") 13 | block_package("#{multi_window}") 14 | end 15 | end 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /lib/device_api/android/plugins/audio.rb: -------------------------------------------------------------------------------- 1 | module DeviceAPI 2 | module Android 3 | module Plugin 4 | class Audio 5 | 6 | attr_reader :qualifier 7 | 8 | def initialize(options) 9 | @qualifier = options #[:serial] 10 | end 11 | 12 | def get_volume_steps 13 | audio = ADB.dumpsys( @qualifier, 'audio' ) 14 | vol_steps = audio.detect { |a| a.include?('volume steps:') } 15 | return nil if vol_steps.nil? 16 | 17 | vol_steps.scan(/volume steps: (.*)/).flatten.first.to_i 18 | end 19 | 20 | def get_current_volume 21 | system = get_system_volume 22 | volume = system.select { |a| a.include?('Current') }.first 23 | volume.scan(/Current: 2:\s(.*?),(:?.*)/).flatten.first.to_i 24 | 25 | end 26 | 27 | def is_muted? 28 | system = get_system_volume 29 | mute = system.select { |a| a.include?('Mute') }.first 30 | mute.scan(/Mute count: (.*)/).flatten.first.to_i > 0 31 | end 32 | 33 | def volume 34 | return 0 if is_muted? 35 | steps = get_volume_steps 36 | vol = get_current_volume 37 | ((vol.to_f / steps.to_f) * 100).to_i 38 | end 39 | 40 | def max_volume 41 | vol = get_current_volume 42 | steps = get_volume_steps 43 | 44 | change_volume(steps - vol, 24) 45 | 46 | get_current_volume == steps 47 | end 48 | 49 | def min_volume 50 | vol = get_current_volume 51 | change_volume(vol, 25) 52 | 53 | get_current_volume == 0 54 | # adb shell service call audio 4 i32 1 i32 0 i32 1 55 | end 56 | 57 | private 58 | 59 | def change_volume(op, key) 60 | op.times do 61 | ADB.keyevent(@qualifier, key ) 62 | end 63 | end 64 | 65 | def get_system_volume 66 | audio = ADB.dumpsys( @qualifier, 'audio' ) 67 | index = audio.index('- STREAM_SYSTEM:') 68 | 69 | return nil if index.nil? 70 | 71 | audio[index+1..index+2] 72 | end 73 | end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/device_api/android/plugins/battery.rb: -------------------------------------------------------------------------------- 1 | module DeviceAPI 2 | module Android 3 | module Plugin 4 | class Battery 5 | attr_reader :current_temp, :max_temp, :max_current, :voltage, :level, :health, :status, :powered 6 | 7 | def initialize(options = {}) 8 | qualifier = options[:qualifier] 9 | props = ADB.get_battery_info(qualifier) 10 | @current_temp = props["temperature"] 11 | @max_temp = props["mBatteryMaxTemp"] 12 | @max_current = props["mBatteryMaxCurrent"] 13 | @voltage = props["voltage"] 14 | @level = props["level"] 15 | @health = props["health"] 16 | @status = props["status"] 17 | @powered = props["USB powered"] 18 | end 19 | end 20 | end 21 | end 22 | end 23 | 24 | -------------------------------------------------------------------------------- /lib/device_api/android/plugins/disk.rb: -------------------------------------------------------------------------------- 1 | module DeviceAPI 2 | module Android 3 | module Plugin 4 | class Disk 5 | 6 | attr_reader :qualifier 7 | def initialize(options = {}) 8 | @qualifier = options[:qualifier] 9 | end 10 | 11 | def process_stats(options = {}) 12 | disk_info = {} 13 | stats = options[:data] || ADB.dumpsys(@qualifier, 'diskstats') 14 | stats.each do |stat| 15 | if /(.*)-.*:\s(.*)\s\/\s([0-9]*[A-Z])\s[a-z]*\s=\s([0-9]*%)/.match(stat) 16 | disk_info["#{Regexp.last_match[1].downcase}_total"] = Regexp.last_match[3] 17 | disk_info["#{Regexp.last_match[1].downcase}_free"] = Regexp.last_match[4] 18 | disk_info["#{Regexp.last_match[1].downcase}_used"] = Regexp.last_match[2] 19 | elsif /(.*):\s(\S*)/.match(stat) 20 | disk_info[Regexp.last_match[1].downcase] = Regexp.last_match[2] 21 | end 22 | end 23 | disk_info 24 | end 25 | 26 | end 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /lib/device_api/android/plugins/memory.rb: -------------------------------------------------------------------------------- 1 | module DeviceAPI 2 | module Android 3 | # Plugins contain extra information about the attached device(s) 4 | module Plugin 5 | # Class used to provide information about process memory usage 6 | # and device memory usage 7 | class Memory 8 | 9 | # Class used for holding process information 10 | class MemInfo 11 | attr_reader :process, :memory, :pid 12 | def initialize(options = {}) 13 | @process = options[:process] 14 | @memory = options[:memory] 15 | @pid = options[:pid] 16 | end 17 | end 18 | 19 | # Class used for storing process information 20 | class RAM 21 | attr_accessor :total, :free, :used, :lost, :tuning 22 | def initialize(options = {}) 23 | @total = options[:total] 24 | @free = options[:free] 25 | @used = options[:used] 26 | @lost = options[:lost] 27 | @tuning = options[:tuning] 28 | end 29 | end 30 | 31 | attr_accessor :processes, :mem_info 32 | 33 | def initialize(options = {}) 34 | @qualifier = options[:qualifier] 35 | info = options[:data] || ADB.dumpsys(@qualifier, 'meminfo') 36 | process_data(info) 37 | end 38 | 39 | def process_data(memory_info) 40 | groups = memory_info.chunk { |a| a == '' }.reject { |a,_| a }.map { |_,b| b } 41 | 42 | raise 'A different ADB result has been received' unless groups[1].first == 'Total PSS by process:' 43 | @processes = [] 44 | process_total_pss_by_process(groups[1]) 45 | process_ram_info(groups[4]) 46 | end 47 | 48 | def update 49 | meminfo = ADB.dumpsys(@qualifier, 'meminfo') 50 | process_data(meminfo) 51 | end 52 | 53 | # Processes memory used by each running process 54 | def process_total_pss_by_process(data) 55 | data.each do |l| 56 | if /(.*):\s+(.*)\s+\(.*pid\s+(\S*).*\)/.match(l) 57 | @processes << MemInfo.new(process: Regexp.last_match[2], memory: Regexp.last_match[1], pid: Regexp.last_match[3] ) 58 | end 59 | end 60 | end 61 | 62 | # Processes memory used by the device 63 | def process_ram_info(data) 64 | ram_info = {} 65 | data.each do |l| 66 | if /Tuning:\s+(.*)/.match(l) 67 | ram_info['tuning'] = Regexp.last_match[1] 68 | elsif /(.*):\s(-?[0-9]*\s\S*)/.match(l) 69 | ram_info[Regexp.last_match[1].downcase] = Regexp.last_match[2] 70 | end 71 | end 72 | @mem_info = RAM.new(total: ram_info['total ram'], free: ram_info['free ram'], used: ram_info['used ram'], lost: ram_info['lost'], tuning: ram_info['tuning']) 73 | end 74 | end 75 | end 76 | end 77 | end -------------------------------------------------------------------------------- /lib/device_api/android/signing.rb: -------------------------------------------------------------------------------- 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 | raise SigningCommandError.new('AAPT not available') unless DeviceAPI::Android::AAPT.aapt_available? 58 | result = execute("aapt list #{apk_path} | grep '^META-INF\/.*'") 59 | return false if result.stdout.empty? 60 | true 61 | end 62 | 63 | # Removes any previous signatures from an apk 64 | # @param [String] apk_path full path to the apk 65 | # @return [Boolean, Exception] returns true if the apk is successfully unsigned, otherwise an exception is raised 66 | def self.unsign_apk(apk_path) 67 | raise SigningCommandError.new('AAPT not available') unless DeviceAPI::Android::AAPT.aapt_available? 68 | file_list = execute("aapt list #{apk_path} | grep '^META-INF\/.*'") 69 | result = execute("aapt remove #{apk_path} #{file_list.stdout.split(/\s+/).join(' ')}") 70 | raise SigningCommandError.new(result.stderr) if result.exit != 0 71 | true 72 | end 73 | end 74 | 75 | # Signing error class 76 | class SigningCommandError < StandardError 77 | def initialize(msg) 78 | super(msg) 79 | end 80 | end 81 | end 82 | end -------------------------------------------------------------------------------- /spec/android_audio_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'device_api/android' 3 | 4 | describe DeviceAPI::Android::Plugin::Audio do 5 | describe 'Audio functions' do 6 | it 'should return volume' do 7 | out = <<-EOF 8 | volume steps: 19 9 | - STREAM_SYSTEM: 10 | Mute count: 0 11 | Current: 2: 19, 40000000: 7, 12 | EOF 13 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 14 | audio = DeviceAPI::Android::Plugin::Audio.new('B0180706345401F5') 15 | expect(audio.volume).to eq(100) 16 | end 17 | 18 | it 'should handle arbitrary volumes' do 19 | volumes = { 20 | '20' => 100, 21 | '15' => 75, 22 | '10' => 50, 23 | '5' => 25 24 | } 25 | 26 | random = rand(volumes.count) 27 | out = <<-EOF 28 | volume steps: 20 29 | - STREAM_SYSTEM: 30 | Mute count: 0 31 | Current: 2: #{volumes.keys[random].to_s}, 400000000: 7, 32 | EOF 33 | 34 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 35 | audio = DeviceAPI::Android::Plugin::Audio.new('B0180706345401F5') 36 | expect(audio.volume).to eq(volumes.values[random]) 37 | end 38 | 39 | it 'should handle a muted device' do 40 | out = <<-EOF 41 | - STREAM_SYSTEM: 42 | Mute count: 1 43 | Current: 2: 0, 40000000: 7, 44 | EOF 45 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 46 | audio = DeviceAPI::Android::Plugin::Audio.new('1234567890') 47 | expect(audio.is_muted?).to eq(true) 48 | end 49 | 50 | it 'should handle a device with no volume' do 51 | out = <<-EOF 52 | volume steps: 19 53 | - STREAM_SYSTEM: 54 | Mute count: 0 55 | Current: 2: 0, 40000000: 7, 56 | EOF 57 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 58 | audio = DeviceAPI::Android::Plugin::Audio.new('1234567890') 59 | expect(audio.volume).to eq(0) 60 | end 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /spec/android_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'device_api/android/device' 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 | it 'can return the Wifi mac address' do 206 | out = < mtu 1452 qdisc noop state DOWN 208 | link/tunnel6 :: brd :: 209 | 5: p2p0: mtu 1500 qdisc pfifo_fast state DORMANT qlen 1000 210 | link/ether 42:b4:cd:73:8f:8b brd ff:ff:ff:ff:ff:ff 211 | inet6 fe80::40b4:cdff:fe73:8f8b/64 scope link 212 | valid_lft forever preferred_lft forever 213 | 6: wlan0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 214 | link/ether fc:c2:de:6a:04:9e brd ff:ff:ff:ff:ff:ff 215 | inet 192.168.101.227/24 brd 192.168.101.255 scope global wlan0 216 | inet6 fe80::42b4:cdff:fe73:8f8b/64 scope link 217 | valid_lft forever preferred_lft forever 218 | EOF 219 | device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290') 220 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 221 | expect(device.wifi_mac_address).to eq('fc:c2:de:6a:04:9e') 222 | end 223 | 224 | it 'can return the Wifi mac address on an Android 6.0 and above device' do 225 | out = <<-EOF 226 | 5: p2p0: mtu 1500 qdisc pfifo_fast state DORMANT qlen 1000 227 | link/ether 42:b4:cd:73:8f:8b brd ff:ff:ff:ff:ff:ff 228 | inet6 fe80::40b4:cdff:fe73:8f8b/64 scope link 229 | valid_lft forever preferred_lft forever 230 | 6: wlan0: mtu 1500 qdisc pfifo_fast state UP qlen 1000 231 | link/ether 00:9A:CD:5E:CC:40 brd ff:ff:ff:ff:ff:ff 232 | inet 192.168.101.227/24 brd 192.168.101.255 scope global wlan0 233 | inet6 fe80::42b4:cdff:fe73:8f8b/64 scope link 234 | valid_lft forever preferred_lft forever 235 | EOF 236 | 237 | device = DeviceAPI::Android::Device.new(serial: 'SH34RW905290') 238 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 239 | expect(device.wifi_mac_address).to eq('00:9A:CD:5E:CC:40') 240 | end 241 | 242 | it 'will not crash if Wifi is not enabled' do 243 | out = < 'device' }]) 31 | end 32 | 33 | it 'returns an an array with multiple items when there are multiple items attached' do 34 | out = <<_______________________________________________________ 35 | List of devices attached 36 | SH34RW905290 device 37 | 123456324 no device 38 | 39 | _______________________________________________________ 40 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 41 | expect(DeviceAPI::Android::ADB.devices).to eq([{ 'SH34RW905290' => 'device' }, { '123456324' => 'no device' }]) 42 | end 43 | 44 | it 'can deal with extra output when adb starts up' do 45 | out = <<_______________________________________________________ 46 | * daemon not running. starting it now on port 5037 * 47 | * daemon started successfully * 48 | List of devices attached 49 | SH34RW905290 device 50 | _______________________________________________________ 51 | allow(Open3).to receive(:capture3) { [out, '', STATUS_ZERO] } 52 | expect(DeviceAPI::Android::ADB.devices).to eq([{ 'SH34RW905290' => 'device' }]) 53 | end 54 | 55 | it 'can deal with no devices connected' do 56 | allow(Open3).to receive(:capture3) { ["error: device not found\n", '', STATUS_ZERO] } 57 | expect(DeviceAPI::Android::ADB.devices).to be_empty 58 | end 59 | end 60 | 61 | describe ".get_uptime" do 62 | it "can process an uptime" do 63 | out = <<_______________________________________________________ 64 | 12307.23 48052.0 65 | _______________________________________________________ 66 | allow(Open3).to receive(:capture3) { [ out, '', STATUS_ZERO] } 67 | expect( DeviceAPI::Android::ADB.get_uptime('SH34RW905290')).to eq( 12307 ) 68 | end 69 | 70 | it 'raises an UnauthorizedDevice exception' do 71 | err = < 5000, :package => 'my.app.package' )).to be_a OpenStruct 192 | end 193 | 194 | it 'raises an UnauthorizedDevice exception' do 195 | err = <