├── .DS_Store ├── LICENSE ├── README.md ├── libs ├── AmazonFling.jar ├── README.md └── WhisperPlay.jar ├── src └── com │ └── connectsdk │ ├── discovery │ └── provider │ │ └── FireTVDiscoveryProvider.java │ └── service │ ├── FireTVService.java │ └── command │ └── FireTVServiceError.java └── test └── src └── com └── connectsdk ├── discovery └── provider │ └── FireTVDiscoveryProviderTest.java └── service ├── FireTVServiceTest.java ├── PlayStateSubscriptionParameterizedTest.java └── PlayStateSubscriptionTest.java /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConnectSDK/Connect-SDK-Android-FireTV/fb17bbf646f69e4ba92651a0b20a0f6db363d86c/.DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (c) 2015 LG Electronics. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Connect-SDK-Android-FireTV 2 | The module extends Connect SDK to add FireTV support. This repository is included as a submodule in 3 | the main project, and has some manual setup before the main project will compile. It provides the 4 | following functionality: 5 | 6 | * Media playback 7 | * Media control 8 | Using Connect SDK for discovery/control of Fire TV devices will result in your app complying with the [Amazon Fling SDK terms of service](https://developer.amazon.com/public/support/pml.html). 9 | 10 | ##General Information 11 | For more information about Connect SDK, visit the [main repository](https://github.com/ConnectSDK/Connect-SDK-Android). This project can be built in Android Studio or directly with Gradle. Eclipse IDE is not supported. 12 | 13 | ##Setup 14 | ###Connect SDK Integration 15 | 1. Setup [Connect-SDK-Android](https://github.com/ConnectSDK/Connect-SDK-Android) 16 | 2. Download AmazonFling.jar and WhisperPlay.jar from [the Amazon website](https://developer.amazon.com/public/apis/experience/fling/docs/amazon-fling-sdk-download) and put them into ```Connect-SDK-Android/modules/firetv/libs/``` folder 17 | 3. Add these lines into dependency section of build.gradle file: 18 | 19 | ```groovy 20 | compile files('modules/firetv/libs/AmazonFling.jar') 21 | compile files('modules/firetv/libs/WhisperPlay.jar') 22 | ``` 23 | 4. Synchronize your project with Gradle files 24 | 25 | ###Connect SDK Lite Integration 26 | 1. Setup [Connect-SDK-Android-Lite](https://github.com/ConnectSDK/Connect-SDK-Android-Lite) 27 | 2. Clone this repository into a subfolder of the Connect SDK Lite project (e.g. modules/firetv) 28 | 3. Go to the [Amazon Developer site](https://developer.amazon.com/) download the AmazonFling.jar and WhisperPlay.jar and put them into ```Connect-SDK-Android/modules/firetv/libs/``` folder 29 | 4. Add these lines into dependency section of build.gradle file: 30 | 31 | ```groovy 32 | compile files('modules/firetv/libs/AmazonFling.jar') 33 | compile files('modules/firetv/libs/WhisperPlay.jar') 34 | ``` 35 | 5. Add sources files for FireTV in your build.gradle, it should looks similar to this (here we have FireTV module in modules/firetv folder): 36 | ```groovy 37 | sourceSets { 38 | main { 39 | manifest.srcFile 'AndroidManifest.xml' 40 | java.srcDirs = [ 41 | 'src', 42 | 'core/src', 43 | 'modules/firetv/src', 44 | ] 45 | resources.srcDirs = ['src'] 46 | aidl.srcDirs = ['src'] 47 | renderscript.srcDirs = ['src'] 48 | res.srcDirs = ['res'] 49 | assets.srcDirs = ['assets'] 50 | } 51 | test { 52 | java.srcDirs = [ 53 | 'core/test/src', 54 | 'modules/firetv/test/src', 55 | ] 56 | } 57 | } 58 | ``` 59 | 60 | 6. In Connect SDK Lite's `DefaultPlatforms.java` file add this line 61 | ```groovy 62 | devicesList.put("com.connectsdk.service.FireTVService", "com.connectsdk.discovery.provider.FireTVDiscoveryProvider"); 63 | ``` 64 | inside `getDeviceServiceMap()` method. 65 | 66 | 67 | ##License 68 | Copyright (c) 2015 LG Electronics. 69 | 70 | Licensed under the Apache License, Version 2.0 (the "License"); 71 | you may not use this file except in compliance with the License. 72 | You may obtain a copy of the License at 73 | 74 | > http://www.apache.org/licenses/LICENSE-2.0 75 | 76 | Unless required by applicable law or agreed to in writing, software 77 | distributed under the License is distributed on an "AS IS" BASIS, 78 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 79 | See the License for the specific language governing permissions and 80 | limitations under the License. 81 | -------------------------------------------------------------------------------- /libs/AmazonFling.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConnectSDK/Connect-SDK-Android-FireTV/fb17bbf646f69e4ba92651a0b20a0f6db363d86c/libs/AmazonFling.jar -------------------------------------------------------------------------------- /libs/README.md: -------------------------------------------------------------------------------- 1 | Please place external frameworks in this directory. 2 | -------------------------------------------------------------------------------- /libs/WhisperPlay.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ConnectSDK/Connect-SDK-Android-FireTV/fb17bbf646f69e4ba92651a0b20a0f6db363d86c/libs/WhisperPlay.jar -------------------------------------------------------------------------------- /src/com/connectsdk/discovery/provider/FireTVDiscoveryProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FireTVDiscoveryProvider 3 | * Connect SDK 4 | * 5 | * Copyright (c) 2015 LG Electronics. 6 | * Created by Oleksii Frolov on 08 Jul 2015 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | package com.connectsdk.discovery.provider; 22 | 23 | import android.content.Context; 24 | 25 | import com.amazon.whisperplay.fling.media.controller.DiscoveryController; 26 | import com.amazon.whisperplay.fling.media.controller.RemoteMediaPlayer; 27 | import com.connectsdk.core.Util; 28 | import com.connectsdk.discovery.DiscoveryFilter; 29 | import com.connectsdk.discovery.DiscoveryProvider; 30 | import com.connectsdk.discovery.DiscoveryProviderListener; 31 | import com.connectsdk.service.FireTVService; 32 | import com.connectsdk.service.command.ServiceCommandError; 33 | import com.connectsdk.service.config.ServiceDescription; 34 | 35 | import java.util.List; 36 | import java.util.concurrent.ConcurrentHashMap; 37 | import java.util.concurrent.CopyOnWriteArrayList; 38 | 39 | /** 40 | * FireTVDiscoveryProvider provides discovery implementation for FireTV devices. 41 | * FireTVDiscoveryProvider acts as a layer on top of Fling SDK, and requires the Fling SDK library 42 | * to function. This implementation does not use discovery filters because only one type of service 43 | * can be discovered. Currently it can discover only FireTV device with default media player 44 | * application. 45 | * 46 | * Using Connect SDK for discovery/control of FireTV devices will result in your app complying with 47 | * the Fling SDK terms of service. 48 | */ 49 | public class FireTVDiscoveryProvider implements DiscoveryProvider { 50 | 51 | private DiscoveryController discoveryController; 52 | 53 | private boolean isRunning; 54 | 55 | DiscoveryController.IDiscoveryListener fireTVListener; 56 | 57 | ConcurrentHashMap foundServices 58 | = new ConcurrentHashMap<>(); 59 | 60 | CopyOnWriteArrayList serviceListeners 61 | = new CopyOnWriteArrayList<>(); 62 | 63 | public FireTVDiscoveryProvider(Context context) { 64 | this(new DiscoveryController(context)); 65 | } 66 | 67 | public FireTVDiscoveryProvider(DiscoveryController discoveryController) { 68 | this.discoveryController = discoveryController; 69 | this.fireTVListener = new FireTVDiscoveryListener(); 70 | } 71 | 72 | /** 73 | * Safely start discovery. Ignore if it's already started. 74 | */ 75 | @Override 76 | public void start() { 77 | if (!isRunning) { 78 | discoveryController.start(fireTVListener); 79 | isRunning = true; 80 | } 81 | } 82 | 83 | /** 84 | * Safely stop discovery and remove all found FireTV services because they don't work when 85 | * discovery is stopped. Ignore if it's already stopped. 86 | */ 87 | @Override 88 | public void stop() { 89 | if (isRunning) { 90 | discoveryController.stop(); 91 | isRunning = false; 92 | } 93 | for (ServiceDescription serviceDescription : foundServices.values()) { 94 | notifyListenersThatServiceLost(serviceDescription); 95 | } 96 | foundServices.clear(); 97 | } 98 | 99 | /** 100 | * Safely restart discovery 101 | */ 102 | @Override 103 | public void restart() { 104 | stop(); 105 | start(); 106 | } 107 | 108 | /** 109 | * Invokes restart method since FlingSDK doesn't have analog of rescan 110 | */ 111 | @Override 112 | public void rescan() { 113 | // discovery controller doesn't have rescan capability 114 | restart(); 115 | } 116 | 117 | /** 118 | * Stop discovery and removes all cached services 119 | */ 120 | @Override 121 | public void reset() { 122 | foundServices.clear(); 123 | stop(); 124 | } 125 | 126 | @Override 127 | public void addListener(DiscoveryProviderListener listener) { 128 | serviceListeners.add(listener); 129 | } 130 | 131 | @Override 132 | public void removeListener(DiscoveryProviderListener listener) { 133 | serviceListeners.remove(listener); 134 | } 135 | 136 | /** 137 | * DiscoveryFilter is not used in current implementation 138 | */ 139 | @Override 140 | public void addDeviceFilter(DiscoveryFilter filter) { 141 | // intentionally left blank 142 | } 143 | 144 | /** 145 | * DiscoveryFilter is not used in current implementation 146 | */ 147 | @Override 148 | public void removeDeviceFilter(DiscoveryFilter filter) { 149 | // intentionally left blank 150 | } 151 | 152 | /** 153 | * DiscoveryFilter is not used in current implementation 154 | */ 155 | @Override 156 | public void setFilters(List filters) { 157 | // intentionally left blank 158 | } 159 | 160 | @Override 161 | public boolean isEmpty() { 162 | return foundServices.isEmpty(); 163 | } 164 | 165 | private void notifyListenersThatServiceAdded(final ServiceDescription serviceDescription) { 166 | Util.runOnUI(new Runnable() { 167 | @Override 168 | public void run() { 169 | for (DiscoveryProviderListener listener : serviceListeners) { 170 | listener.onServiceAdded(FireTVDiscoveryProvider.this, serviceDescription); 171 | } 172 | } 173 | }); 174 | } 175 | 176 | private void notifyListenersThatServiceLost(final ServiceDescription serviceDescription) { 177 | Util.runOnUI(new Runnable() { 178 | @Override 179 | public void run() { 180 | for (DiscoveryProviderListener listener : serviceListeners) { 181 | listener.onServiceRemoved(FireTVDiscoveryProvider.this, serviceDescription); 182 | } 183 | } 184 | }); 185 | } 186 | 187 | private void notifyListenersThatDiscoveryFailed(final ServiceCommandError error) { 188 | Util.runOnUI(new Runnable() { 189 | @Override 190 | public void run() { 191 | for (DiscoveryProviderListener listener : serviceListeners) { 192 | listener.onServiceDiscoveryFailed(FireTVDiscoveryProvider.this, error); 193 | } 194 | } 195 | }); 196 | } 197 | 198 | 199 | class FireTVDiscoveryListener implements DiscoveryController.IDiscoveryListener { 200 | 201 | @Override 202 | public void playerDiscovered(RemoteMediaPlayer remoteMediaPlayer) { 203 | if (remoteMediaPlayer == null) { 204 | return; 205 | } 206 | String uid = remoteMediaPlayer.getUniqueIdentifier(); 207 | ServiceDescription serviceDescription = foundServices.get(uid); 208 | 209 | if (serviceDescription == null) { 210 | serviceDescription = new ServiceDescription(); 211 | updateServiceDescription(serviceDescription, remoteMediaPlayer); 212 | foundServices.put(uid, serviceDescription); 213 | notifyListenersThatServiceAdded(serviceDescription); 214 | } else { 215 | updateServiceDescription(serviceDescription, remoteMediaPlayer); 216 | } 217 | } 218 | 219 | @Override 220 | public void playerLost(RemoteMediaPlayer remoteMediaPlayer) { 221 | if (remoteMediaPlayer == null) { 222 | return; 223 | } 224 | ServiceDescription serviceDescription 225 | = foundServices.get(remoteMediaPlayer.getUniqueIdentifier()); 226 | if (serviceDescription != null) { 227 | notifyListenersThatServiceLost(serviceDescription); 228 | foundServices.remove(remoteMediaPlayer.getUniqueIdentifier()); 229 | } 230 | } 231 | 232 | @Override 233 | public void discoveryFailure() { 234 | final ServiceCommandError error = new ServiceCommandError("FireTV discovery failure"); 235 | notifyListenersThatDiscoveryFailed(error); 236 | } 237 | 238 | private void updateServiceDescription(ServiceDescription serviceDescription, 239 | RemoteMediaPlayer remoteMediaPlayer) { 240 | String uid = remoteMediaPlayer.getUniqueIdentifier(); 241 | serviceDescription.setDevice(remoteMediaPlayer); 242 | serviceDescription.setFriendlyName(remoteMediaPlayer.getName()); 243 | serviceDescription.setIpAddress(uid); 244 | serviceDescription.setServiceID(FireTVService.ID); 245 | serviceDescription.setUUID(uid); 246 | } 247 | 248 | } 249 | 250 | } 251 | -------------------------------------------------------------------------------- /src/com/connectsdk/service/FireTVService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FireTVService 3 | * Connect SDK 4 | * 5 | * Copyright (c) 2015 LG Electronics. 6 | * Created by Oleksii Frolov on 08 Jul 2015 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | package com.connectsdk.service; 22 | 23 | 24 | import com.amazon.whisperplay.fling.media.controller.RemoteMediaPlayer; 25 | import com.amazon.whisperplay.fling.media.service.CustomMediaPlayer; 26 | import com.amazon.whisperplay.fling.media.service.MediaPlayerInfo; 27 | import com.amazon.whisperplay.fling.media.service.MediaPlayerStatus; 28 | import com.connectsdk.core.ImageInfo; 29 | import com.connectsdk.core.MediaInfo; 30 | import com.connectsdk.core.Util; 31 | import com.connectsdk.discovery.DiscoveryFilter; 32 | import com.connectsdk.service.capability.CapabilityMethods; 33 | import com.connectsdk.service.capability.MediaControl; 34 | import com.connectsdk.service.capability.MediaPlayer; 35 | import com.connectsdk.service.capability.listeners.ResponseListener; 36 | import com.connectsdk.service.command.FireTVServiceError; 37 | import com.connectsdk.service.command.ServiceCommandError; 38 | import com.connectsdk.service.command.ServiceSubscription; 39 | import com.connectsdk.service.config.ServiceConfig; 40 | import com.connectsdk.service.config.ServiceDescription; 41 | import com.connectsdk.service.sessions.LaunchSession; 42 | 43 | import org.json.JSONArray; 44 | import org.json.JSONException; 45 | import org.json.JSONObject; 46 | 47 | import java.util.ArrayList; 48 | import java.util.List; 49 | import java.util.concurrent.ExecutionException; 50 | import java.util.concurrent.Future; 51 | 52 | /** 53 | * FireTVService provides capabilities for FireTV devices. FireTVService acts as a layer on top of 54 | * Fling SDK, and requires the Fling SDK library to function. FireTVService provides the following 55 | * functionality: 56 | * - Media playback 57 | * - Media control 58 | * 59 | * Using Connect SDK for discovery/control of FireTV devices will result in your app complying with 60 | * the Fling SDK terms of service. 61 | */ 62 | public class FireTVService extends DeviceService implements MediaPlayer, MediaControl { 63 | 64 | public static final String ID = "FireTV"; 65 | 66 | private static final String META_TITLE = "title"; 67 | private static final String META_DESCRIPTION = "description"; 68 | private static final String META_MIME_TYPE = "type"; 69 | private static final String META_ICON_IMAGE = "poster"; 70 | private static final String META_NOREPLAY = "noreplay"; 71 | private static final String META_TRACKS = "tracks"; 72 | private static final String META_SRC = "src"; 73 | private static final String META_KIND = "kind"; 74 | private static final String META_SRCLANG = "srclang"; 75 | private static final String META_LABEL = "label"; 76 | 77 | private final RemoteMediaPlayer remoteMediaPlayer; 78 | private PlayStateSubscription playStateSubscription; 79 | 80 | public FireTVService(ServiceDescription serviceDescription, ServiceConfig serviceConfig) { 81 | super(serviceDescription, serviceConfig); 82 | if (serviceDescription != null 83 | && serviceDescription.getDevice() instanceof RemoteMediaPlayer) { 84 | this.remoteMediaPlayer = (RemoteMediaPlayer) serviceDescription.getDevice(); 85 | } else { 86 | this.remoteMediaPlayer = null; 87 | } 88 | } 89 | 90 | /** 91 | * Get filter instance for this service which contains a name of service and id. It is used in 92 | * discovery process 93 | */ 94 | public static DiscoveryFilter discoveryFilter() { 95 | return new DiscoveryFilter(ID, "FireTV"); 96 | } 97 | 98 | /** 99 | * Prepare a service for usage 100 | */ 101 | @Override 102 | public void connect() { 103 | super.connect(); 104 | if (remoteMediaPlayer != null) { 105 | connected = true; 106 | reportConnected(connected); 107 | } 108 | } 109 | 110 | /** 111 | * Check if service is ready 112 | */ 113 | @Override 114 | public boolean isConnected() { 115 | return connected; 116 | } 117 | 118 | /** 119 | * Check if service implements connect/disconnect methods 120 | */ 121 | @Override 122 | public boolean isConnectable() { 123 | return true; 124 | } 125 | 126 | /** 127 | * Disconnect a service and close all subscriptions 128 | */ 129 | @Override 130 | public void disconnect() { 131 | super.disconnect(); 132 | if (playStateSubscription != null) { 133 | playStateSubscription.unsubscribe(); 134 | playStateSubscription = null; 135 | } 136 | connected = false; 137 | } 138 | 139 | @Override 140 | protected void updateCapabilities() { 141 | List capabilities = new ArrayList(); 142 | capabilities.add(MediaPlayer.MediaInfo_Get); 143 | capabilities.add(MediaPlayer.Display_Image); 144 | capabilities.add(MediaPlayer.Play_Audio); 145 | capabilities.add(MediaPlayer.Play_Video); 146 | capabilities.add(MediaPlayer.Close); 147 | capabilities.add(MediaPlayer.MetaData_MimeType); 148 | capabilities.add(MediaPlayer.MetaData_Thumbnail); 149 | capabilities.add(MediaPlayer.MetaData_Title); 150 | capabilities.add(MediaPlayer.Subtitle_WebVTT); 151 | 152 | capabilities.add(MediaControl.Play); 153 | capabilities.add(MediaControl.Pause); 154 | capabilities.add(MediaControl.Stop); 155 | capabilities.add(MediaControl.Seek); 156 | capabilities.add(MediaControl.Duration); 157 | capabilities.add(MediaControl.Position); 158 | capabilities.add(MediaControl.PlayState); 159 | capabilities.add(MediaControl.PlayState_Subscribe); 160 | 161 | setCapabilities(capabilities); 162 | } 163 | 164 | /** 165 | * Get a priority level for a particular capability 166 | */ 167 | @Override 168 | public CapabilityPriorityLevel getPriorityLevel(Class clazz) { 169 | if (clazz != null) { 170 | if (clazz.equals(MediaPlayer.class)) { 171 | return getMediaPlayerCapabilityLevel(); 172 | } else if (clazz.equals(MediaControl.class)) { 173 | return getMediaControlCapabilityLevel(); 174 | } 175 | } 176 | return CapabilityPriorityLevel.NOT_SUPPORTED; 177 | } 178 | 179 | 180 | /** 181 | * Get MediaPlayer implementation 182 | */ 183 | @Override 184 | public MediaPlayer getMediaPlayer() { 185 | return this; 186 | } 187 | 188 | /** 189 | * Get MediaPlayer priority level 190 | */ 191 | @Override 192 | public CapabilityPriorityLevel getMediaPlayerCapabilityLevel() { 193 | return CapabilityPriorityLevel.HIGH; 194 | } 195 | 196 | /** 197 | * Get MediaInfo available only during playback otherwise returns an error 198 | * @param listener 199 | */ 200 | @Override 201 | public void getMediaInfo(final MediaInfoListener listener) { 202 | final String error = "Error getting media info"; 203 | RemoteMediaPlayer.AsyncFuture asyncFuture = null; 204 | try { 205 | asyncFuture = remoteMediaPlayer.getMediaInfo(); 206 | handleAsyncFutureWithConversion(listener, asyncFuture, 207 | new ConvertResult() { 208 | @Override 209 | public MediaInfo convert(MediaPlayerInfo data) throws JSONException { 210 | JSONObject metaJson = null; 211 | metaJson = new JSONObject(data.getMetadata()); 212 | List images = null; 213 | if (metaJson.has(META_ICON_IMAGE)) { 214 | images = new ArrayList(); 215 | images.add(new ImageInfo(metaJson.getString(META_ICON_IMAGE))); 216 | } 217 | MediaInfo mediaInfo = new MediaInfo(data.getSource(), 218 | metaJson.getString(META_MIME_TYPE), metaJson.getString(META_TITLE), 219 | metaJson.getString(META_DESCRIPTION), images); 220 | return mediaInfo; 221 | } 222 | }, error); 223 | } catch (Exception e) { 224 | Util.postError(listener, new FireTVServiceError(error)); 225 | return; 226 | } 227 | } 228 | 229 | /** 230 | * Not supported 231 | */ 232 | @Override 233 | public ServiceSubscription subscribeMediaInfo(MediaInfoListener listener) { 234 | Util.postError(listener, ServiceCommandError.notSupported()); 235 | return null; 236 | } 237 | 238 | /** 239 | * Display an image with metadata 240 | * @param url media source 241 | * @param mimeType 242 | * @param title 243 | * @param description 244 | * @param iconSrc 245 | * @param listener 246 | */ 247 | @Override 248 | public void displayImage(String url, String mimeType, String title, String description, 249 | String iconSrc, final LaunchListener listener) { 250 | setMediaSource(new MediaInfo.Builder(url, mimeType) 251 | .setTitle(title) 252 | .setDescription(description) 253 | .setIcon(iconSrc) 254 | .build(), listener); 255 | } 256 | 257 | /** 258 | * Play audio/video 259 | * @param url media source 260 | * @param mimeType 261 | * @param title 262 | * @param description 263 | * @param iconSrc 264 | * @param shouldLoop skipped in current implementation 265 | * @param listener 266 | */ 267 | @Override 268 | public void playMedia(String url, String mimeType, String title, String description, 269 | String iconSrc, boolean shouldLoop, LaunchListener listener) { 270 | setMediaSource(new MediaInfo.Builder(url, mimeType) 271 | .setTitle(title) 272 | .setDescription(description) 273 | .setIcon(iconSrc) 274 | .build(), listener); 275 | } 276 | 277 | /** 278 | * Stop and close media player on FireTV. In current implementation it's similar to stop method 279 | * @param launchSession 280 | * @param listener 281 | */ 282 | @Override 283 | public void closeMedia(LaunchSession launchSession, final ResponseListener listener) { 284 | stop(listener); 285 | } 286 | 287 | /** 288 | * Display an image with metadata 289 | * @param mediaInfo 290 | * @param listener 291 | */ 292 | @Override 293 | public void displayImage(MediaInfo mediaInfo, LaunchListener listener) { 294 | setMediaSource(mediaInfo, listener); 295 | } 296 | 297 | /** 298 | * Play audio/video 299 | * @param mediaInfo 300 | * @param shouldLoop skipped in current implementation 301 | * @param listener 302 | */ 303 | @Override 304 | public void playMedia(MediaInfo mediaInfo, boolean shouldLoop, LaunchListener listener) { 305 | setMediaSource(mediaInfo, listener); 306 | } 307 | 308 | /** 309 | * Get MediaControl capability. It should be used only during media playback. 310 | */ 311 | @Override 312 | public MediaControl getMediaControl() { 313 | return this; 314 | } 315 | 316 | /** 317 | * Get MediaControl priority level 318 | */ 319 | @Override 320 | public CapabilityPriorityLevel getMediaControlCapabilityLevel() { 321 | return CapabilityPriorityLevel.HIGH; 322 | } 323 | 324 | /** 325 | * Play current media. 326 | */ 327 | @Override 328 | public void play(ResponseListener listener) { 329 | final String error = "Error playing"; 330 | RemoteMediaPlayer.AsyncFuture asyncFuture = null; 331 | try { 332 | asyncFuture = remoteMediaPlayer.play(); 333 | handleVoidAsyncFuture(listener, asyncFuture, error); 334 | } catch (Exception e) { 335 | Util.postError(listener, new FireTVServiceError(error, e)); 336 | } 337 | } 338 | 339 | /** 340 | * Pause current media. 341 | */ 342 | @Override 343 | public void pause(ResponseListener listener) { 344 | final String error = "Error pausing"; 345 | RemoteMediaPlayer.AsyncFuture asyncFuture = null; 346 | try { 347 | asyncFuture = remoteMediaPlayer.pause(); 348 | handleVoidAsyncFuture(listener, asyncFuture, error); 349 | } catch (Exception e) { 350 | Util.postError(listener, new FireTVServiceError(error, e)); 351 | } 352 | } 353 | 354 | /** 355 | * Stop current media and close FireTV application. 356 | */ 357 | @Override 358 | public void stop(ResponseListener listener) { 359 | final String error = "Error stopping"; 360 | RemoteMediaPlayer.AsyncFuture asyncFuture = null; 361 | try { 362 | asyncFuture = remoteMediaPlayer.stop(); 363 | handleVoidAsyncFuture(listener, asyncFuture, error); 364 | } catch (Exception e) { 365 | Util.postError(listener, new FireTVServiceError(error, e)); 366 | } 367 | } 368 | 369 | /** 370 | * Not supported 371 | */ 372 | @Override 373 | public void rewind(ResponseListener listener) { 374 | Util.postError(listener, ServiceCommandError.notSupported()); 375 | } 376 | 377 | /** 378 | * Not supported 379 | */ 380 | @Override 381 | public void fastForward(ResponseListener listener) { 382 | Util.postError(listener, ServiceCommandError.notSupported()); 383 | } 384 | 385 | /** 386 | * Not supported 387 | */ 388 | @Override 389 | public void previous(ResponseListener listener) { 390 | Util.postError(listener, ServiceCommandError.notSupported()); 391 | } 392 | 393 | /** 394 | * Not supported 395 | */ 396 | @Override 397 | public void next(ResponseListener listener) { 398 | Util.postError(listener, ServiceCommandError.notSupported()); 399 | } 400 | 401 | /** 402 | * Seek current media. 403 | * @param position time in milliseconds 404 | * @param listener 405 | */ 406 | @Override 407 | public void seek(long position, ResponseListener listener) { 408 | final String error = "Error seeking"; 409 | RemoteMediaPlayer.AsyncFuture asyncFuture = null; 410 | try { 411 | asyncFuture = remoteMediaPlayer.seek(CustomMediaPlayer.PlayerSeekMode.Absolute, 412 | position); 413 | handleVoidAsyncFuture(listener, asyncFuture, error); 414 | } catch (Exception e) { 415 | Util.postError(listener, new FireTVServiceError(error, e)); 416 | } 417 | } 418 | 419 | /** 420 | * Get current media duration. 421 | */ 422 | @Override 423 | public void getDuration(final DurationListener listener) { 424 | final String error = "Error getting duration"; 425 | RemoteMediaPlayer.AsyncFuture asyncFuture; 426 | try { 427 | asyncFuture = remoteMediaPlayer.getDuration(); 428 | handleAsyncFuture(listener, asyncFuture, error); 429 | } catch (Exception e) { 430 | Util.postError(listener, new FireTVServiceError(error, e)); 431 | return; 432 | } 433 | } 434 | 435 | /** 436 | * Get playback position 437 | */ 438 | @Override 439 | public void getPosition(final PositionListener listener) { 440 | final String error = "Error getting position"; 441 | RemoteMediaPlayer.AsyncFuture asyncFuture; 442 | try { 443 | asyncFuture = remoteMediaPlayer.getPosition(); 444 | handleAsyncFuture(listener, asyncFuture, error); 445 | } catch (Exception e) { 446 | Util.postError(listener, new FireTVServiceError(error, e)); 447 | return; 448 | } 449 | } 450 | 451 | /** 452 | * Get playback state 453 | */ 454 | @Override 455 | public void getPlayState(final PlayStateListener listener) { 456 | final String error = "Error getting play state"; 457 | RemoteMediaPlayer.AsyncFuture asyncFuture; 458 | try { 459 | asyncFuture = remoteMediaPlayer.getStatus(); 460 | handleAsyncFutureWithConversion(listener, asyncFuture, 461 | new ConvertResult() { 462 | @Override 463 | public PlayStateStatus convert(MediaPlayerStatus data) { 464 | return createPlayStateStatusFromFireTVStatus(data); 465 | } 466 | }, error); 467 | } catch (Exception e) { 468 | Util.postError(listener, new FireTVServiceError(error, e)); 469 | return; 470 | } 471 | } 472 | 473 | /** 474 | * Subscribe to playback state. Only single instance of subscription is available. Each new 475 | * call returns the same subscription object. 476 | */ 477 | @Override 478 | public ServiceSubscription subscribePlayState( 479 | final PlayStateListener listener) { 480 | 481 | if (playStateSubscription == null) { 482 | playStateSubscription = new PlayStateSubscription(listener); 483 | remoteMediaPlayer.addStatusListener(playStateSubscription); 484 | } else if (!playStateSubscription.getListeners().contains(listener)) { 485 | playStateSubscription.addListener(listener); 486 | } 487 | getPlayState(listener); 488 | return playStateSubscription; 489 | } 490 | 491 | PlayStateStatus createPlayStateStatusFromFireTVStatus(MediaPlayerStatus status) { 492 | PlayStateStatus playState = PlayStateStatus.Unknown; 493 | if (status != null) { 494 | switch (status.getState()) { 495 | case PreparingMedia: 496 | playState = PlayStateStatus.Buffering; 497 | break; 498 | case Playing: 499 | playState = PlayStateStatus.Playing; 500 | break; 501 | case Paused: 502 | playState = PlayStateStatus.Paused; 503 | break; 504 | case Finished: 505 | playState = PlayStateStatus.Finished; 506 | break; 507 | case NoSource: 508 | playState = PlayStateStatus.Idle; 509 | } 510 | } 511 | return playState; 512 | } 513 | 514 | private String getMetadata(MediaInfo mediaInfo) 515 | throws JSONException { 516 | JSONObject json = new JSONObject(); 517 | if (mediaInfo.getTitle() != null && !mediaInfo.getTitle().isEmpty()) { 518 | json.put(META_TITLE, mediaInfo.getTitle()); 519 | } 520 | if (mediaInfo.getDescription() != null && !mediaInfo.getDescription().isEmpty()) { 521 | json.put(META_DESCRIPTION, mediaInfo.getDescription()); 522 | } 523 | json.put(META_MIME_TYPE, mediaInfo.getMimeType()); 524 | if (mediaInfo.getImages() != null && mediaInfo.getImages().size() > 0) { 525 | ImageInfo image = mediaInfo.getImages().get(0); 526 | if (image != null) { 527 | if (image.getUrl() != null && !image.getUrl().isEmpty()) { 528 | json.put(META_ICON_IMAGE, image.getUrl()); 529 | } 530 | } 531 | } 532 | json.put(META_NOREPLAY, true); 533 | if (mediaInfo.getSubtitleInfo() != null) { 534 | JSONArray tracksArray = new JSONArray(); 535 | JSONObject trackObj = new JSONObject(); 536 | trackObj.put(META_KIND, "subtitles"); 537 | trackObj.put(META_SRC, mediaInfo.getSubtitleInfo().getUrl()); 538 | String label = mediaInfo.getSubtitleInfo().getLabel(); 539 | trackObj.put(META_LABEL, label == null ? "" : label); 540 | String language = mediaInfo.getSubtitleInfo().getLanguage(); 541 | trackObj.put(META_SRCLANG, language == null ? "" : language); 542 | tracksArray.put(trackObj); 543 | json.put(META_TRACKS, tracksArray); 544 | } 545 | return json.toString(); 546 | } 547 | 548 | private MediaLaunchObject createMediaLaunchObject() { 549 | LaunchSession launchSession = new LaunchSession(); 550 | launchSession.setService(this); 551 | launchSession.setSessionType(LaunchSession.LaunchSessionType.Media); 552 | launchSession.setAppId(remoteMediaPlayer.getUniqueIdentifier()); 553 | launchSession.setAppName(remoteMediaPlayer.getName()); 554 | MediaLaunchObject mediaLaunchObject = new MediaLaunchObject(launchSession, this); 555 | return mediaLaunchObject; 556 | } 557 | 558 | private void setMediaSource(MediaInfo mediaInfo, final LaunchListener listener) { 559 | final String error = "Error setting media source"; 560 | RemoteMediaPlayer.AsyncFuture asyncFuture = null; 561 | try { 562 | final String metadata = getMetadata(mediaInfo); 563 | asyncFuture = remoteMediaPlayer.setMediaSource(mediaInfo.getUrl(), metadata, true, false); 564 | } catch (Exception e) { 565 | Util.postError(listener, new FireTVServiceError(error, e)); 566 | return; 567 | } 568 | handleAsyncFutureWithConversion(listener, asyncFuture, 569 | new ConvertResult() { 570 | @Override 571 | public MediaLaunchObject convert(Void data) { 572 | return createMediaLaunchObject(); 573 | } 574 | }, error); 575 | } 576 | 577 | private void handleVoidAsyncFuture(final ResponseListener listener, 578 | final RemoteMediaPlayer.AsyncFuture asyncFuture, 579 | final String errorMessage) { 580 | handleAsyncFutureWithConversion(listener, asyncFuture, new ConvertResult() { 581 | @Override 582 | public Object convert(Void data) { 583 | return data; 584 | } 585 | }, errorMessage); 586 | } 587 | 588 | private void handleAsyncFuture(final ResponseListener listener, 589 | final RemoteMediaPlayer.AsyncFuture asyncFuture, 590 | final String errorMessage) { 591 | handleAsyncFutureWithConversion(listener, asyncFuture, new ConvertResult() { 592 | @Override 593 | public T convert(T data) { 594 | return data; 595 | } 596 | }, errorMessage); 597 | } 598 | 599 | private void handleAsyncFutureWithConversion( 600 | final ResponseListener listener, 601 | final RemoteMediaPlayer.AsyncFuture asyncFuture, 602 | final ConvertResult conversion, 603 | final String errorMessage) { 604 | if (asyncFuture != null) { 605 | asyncFuture.getAsync(new RemoteMediaPlayer.FutureListener() { 606 | @Override 607 | public void futureIsNow(Future future) { 608 | try { 609 | Result result = future.get(); 610 | Util.postSuccess(listener, conversion.convert(result)); 611 | } catch (ExecutionException e) { 612 | Util.postError(listener, new FireTVServiceError(errorMessage, 613 | e.getCause())); 614 | } catch (Exception e) { 615 | Util.postError(listener, new FireTVServiceError(errorMessage, e)); 616 | } 617 | } 618 | }); 619 | } else { 620 | Util.postError(listener, new FireTVServiceError(errorMessage)); 621 | } 622 | } 623 | 624 | private interface ConvertResult { 625 | Response convert(Result data) throws Exception; 626 | } 627 | 628 | private abstract static class Subscription> 629 | implements ServiceSubscription { 630 | 631 | List listeners = new ArrayList(); 632 | 633 | Status prevStatus; 634 | 635 | public Subscription(Listener listener) { 636 | if (listener != null) { 637 | this.listeners.add(listener); 638 | } 639 | } 640 | 641 | synchronized void notifyListeners(final Status status) { 642 | if (!status.equals(prevStatus)) { 643 | Util.runOnUI(new Runnable() { 644 | @Override 645 | public void run() { 646 | for (Listener listener : listeners) { 647 | listener.onSuccess(status); 648 | } 649 | } 650 | }); 651 | prevStatus = status; 652 | } 653 | } 654 | 655 | @Override 656 | public Listener addListener(Listener listener) { 657 | if (listener != null) { 658 | listeners.add(listener); 659 | } 660 | return listener; 661 | } 662 | 663 | @Override 664 | public void removeListener(Listener listener) { 665 | listeners.remove(listener); 666 | } 667 | 668 | @Override 669 | public List getListeners() { 670 | return listeners; 671 | } 672 | } 673 | 674 | /** 675 | * Internal play state subscription implementation 676 | */ 677 | class PlayStateSubscription extends Subscription 678 | implements CustomMediaPlayer.StatusListener { 679 | 680 | public PlayStateSubscription(PlayStateListener listener) { 681 | super(listener); 682 | } 683 | 684 | @Override 685 | public void onStatusChange(MediaPlayerStatus mediaPlayerStatus, long position) { 686 | final PlayStateStatus status = createPlayStateStatusFromFireTVStatus(mediaPlayerStatus); 687 | notifyListeners(status); 688 | } 689 | 690 | @Override 691 | public void unsubscribe() { 692 | remoteMediaPlayer.removeStatusListener(this); 693 | playStateSubscription = null; 694 | } 695 | 696 | } 697 | 698 | } 699 | -------------------------------------------------------------------------------- /src/com/connectsdk/service/command/FireTVServiceError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FireTVServiceError 3 | * Connect SDK 4 | * 5 | * Copyright (c) 2015 LG Electronics. 6 | * Created by Oleksii Frolov on 08 Jul 2015 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | package com.connectsdk.service.command; 22 | 23 | /** 24 | * This class implements an exception for FireTVService 25 | */ 26 | public class FireTVServiceError extends ServiceCommandError { 27 | 28 | public FireTVServiceError(String message) { 29 | super(message); 30 | } 31 | 32 | public FireTVServiceError(String message, Throwable e) { 33 | super(message); 34 | this.payload = e; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/src/com/connectsdk/discovery/provider/FireTVDiscoveryProviderTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FireTVDiscoveryProviderTest 3 | * Connect SDK 4 | * 5 | * Copyright (c) 2015 LG Electronics. 6 | * Created by Oleksii Frolov on 08 Jul 2015 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | package com.connectsdk.discovery.provider; 22 | 23 | import com.amazon.whisperplay.fling.media.controller.DiscoveryController; 24 | import com.amazon.whisperplay.fling.media.controller.RemoteMediaPlayer; 25 | import com.connectsdk.discovery.DiscoveryProvider; 26 | import com.connectsdk.discovery.DiscoveryProviderListener; 27 | import com.connectsdk.service.FireTVService; 28 | import com.connectsdk.service.command.ServiceCommandError; 29 | import com.connectsdk.service.config.ServiceDescription; 30 | 31 | import junit.framework.Assert; 32 | 33 | import org.junit.Before; 34 | import org.junit.Test; 35 | import org.junit.runner.RunWith; 36 | import org.mockito.ArgumentCaptor; 37 | import org.mockito.Mockito; 38 | import org.robolectric.Robolectric; 39 | import org.robolectric.RobolectricTestRunner; 40 | import org.robolectric.annotation.Config; 41 | 42 | 43 | @RunWith(RobolectricTestRunner.class) 44 | @Config(manifest=Config.NONE) 45 | public class FireTVDiscoveryProviderTest { 46 | 47 | private FireTVDiscoveryProvider provider; 48 | 49 | private DiscoveryController controller; 50 | 51 | @Before 52 | public void setUp() { 53 | controller = Mockito.mock(DiscoveryController.class); 54 | provider = new FireTVDiscoveryProvider(controller); 55 | } 56 | 57 | @Test 58 | public void testStartWhenNotRunning() { 59 | provider.start(); 60 | Mockito.verify(controller, Mockito.times(1)).start(provider.fireTVListener); 61 | } 62 | 63 | @Test 64 | public void testStartWhenRunning() { 65 | provider.start(); 66 | provider.start(); 67 | Mockito.verify(controller, Mockito.times(1)).start(provider.fireTVListener); 68 | } 69 | 70 | @Test 71 | public void testStopWhenNotRunning() { 72 | provider.stop(); 73 | Mockito.verify(controller, Mockito.times(0)).stop(); 74 | } 75 | 76 | @Test 77 | public void testStopWhenRunning() { 78 | provider.start(); 79 | provider.stop(); 80 | Mockito.verify(controller, Mockito.times(1)).stop(); 81 | } 82 | 83 | @Test 84 | public void testStopWithFoundServices() { 85 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 86 | provider.addListener(listener); 87 | 88 | final ServiceDescription service1 = Mockito.mock(ServiceDescription.class); 89 | provider.foundServices.put("service1", service1); 90 | final ServiceDescription service2 = Mockito.mock(ServiceDescription.class); 91 | provider.foundServices.put("service2", service2); 92 | 93 | provider.stop(); 94 | 95 | Mockito.verify(controller, Mockito.times(0)).stop(); 96 | Mockito.verify(listener).onServiceRemoved(provider, service1); 97 | Mockito.verify(listener).onServiceRemoved(provider, service2); 98 | Assert.assertTrue(provider.foundServices.isEmpty()); 99 | } 100 | 101 | @Test 102 | public void testAddListener() { 103 | // given 104 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 105 | Assert.assertEquals(0, provider.serviceListeners.size()); 106 | 107 | // when 108 | provider.addListener(listener); 109 | 110 | // then 111 | Assert.assertEquals(1, provider.serviceListeners.size()); 112 | } 113 | 114 | @Test 115 | public void testRemoveListener() { 116 | // given 117 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 118 | provider.addListener(listener); 119 | 120 | // when 121 | provider.removeListener(listener); 122 | 123 | // then 124 | Assert.assertEquals(0, provider.serviceListeners.size()); 125 | } 126 | 127 | @Test 128 | public void testDiscoveryPlayerDiscovered() { 129 | // given 130 | RemoteMediaPlayer remoteMediaPlayer = mockRemoteMediaPlayer(); 131 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 132 | provider.addListener(listener); 133 | ArgumentCaptor argDiscoveryProvider = ArgumentCaptor 134 | .forClass(FireTVDiscoveryProvider.class); 135 | ArgumentCaptor argServiceDescription = ArgumentCaptor 136 | .forClass(ServiceDescription.class); 137 | 138 | // when 139 | provider.fireTVListener.playerDiscovered(remoteMediaPlayer); 140 | 141 | // then 142 | Mockito.verify(listener).onServiceAdded(argDiscoveryProvider.capture(), 143 | argServiceDescription.capture()); 144 | 145 | // check all required fields in service description 146 | ServiceDescription serviceDescription = argServiceDescription.getValue(); 147 | Assert.assertSame(provider, argDiscoveryProvider.getValue()); 148 | Assert.assertSame(remoteMediaPlayer, serviceDescription.getDevice()); 149 | Assert.assertFalse(provider.foundServices.isEmpty()); 150 | Assert.assertEquals("FireTVDevice", serviceDescription.getFriendlyName()); 151 | Assert.assertEquals("UID", serviceDescription.getIpAddress()); 152 | Assert.assertEquals("UID", serviceDescription.getUUID()); 153 | Assert.assertEquals(FireTVService.ID, serviceDescription.getServiceID()); 154 | Assert.assertEquals(1, provider.foundServices.size()); 155 | } 156 | 157 | @Test 158 | public void testDiscoveryPlayerDiscoveredTwice() { 159 | // given 160 | RemoteMediaPlayer remoteMediaPlayer = mockRemoteMediaPlayer(); 161 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 162 | provider.addListener(listener); 163 | ArgumentCaptor argDiscoveryProvider = ArgumentCaptor 164 | .forClass(FireTVDiscoveryProvider.class); 165 | ArgumentCaptor argServiceDescription = ArgumentCaptor 166 | .forClass(ServiceDescription.class); 167 | Assert.assertEquals(0, provider.foundServices.size()); 168 | 169 | // when 170 | provider.fireTVListener.playerDiscovered(remoteMediaPlayer); 171 | Assert.assertEquals(1, provider.foundServices.size()); 172 | 173 | Mockito.when(remoteMediaPlayer.getName()).thenReturn("UpdatedField"); 174 | provider.fireTVListener.playerDiscovered(remoteMediaPlayer); 175 | 176 | // then 177 | Mockito.verify(listener).onServiceAdded(argDiscoveryProvider.capture(), 178 | argServiceDescription.capture()); 179 | 180 | // check all required fields in service description 181 | ServiceDescription serviceDescription = argServiceDescription.getValue(); 182 | Assert.assertSame(provider, argDiscoveryProvider.getValue()); 183 | Assert.assertSame(remoteMediaPlayer, serviceDescription.getDevice()); 184 | Assert.assertFalse(provider.foundServices.isEmpty()); 185 | Assert.assertEquals("UpdatedField", serviceDescription.getFriendlyName()); 186 | Assert.assertEquals("UID", serviceDescription.getIpAddress()); 187 | Assert.assertEquals("UID", serviceDescription.getUUID()); 188 | Assert.assertEquals(FireTVService.ID, serviceDescription.getServiceID()); 189 | Assert.assertEquals(1, provider.foundServices.size()); 190 | } 191 | 192 | @Test 193 | public void testDiscoveryNullPlayerDiscovered() { 194 | // given 195 | RemoteMediaPlayer remoteMediaPlayer = null; 196 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 197 | provider.addListener(listener); 198 | 199 | try { 200 | provider.fireTVListener.playerDiscovered(remoteMediaPlayer); 201 | } catch (Exception e) { 202 | Assert.fail("playerLost should not throw exceptions"); 203 | } 204 | 205 | // when 206 | Mockito.verify(listener, Mockito.times(0)).onServiceAdded( 207 | Mockito.any(DiscoveryProvider.class), 208 | Mockito.any(ServiceDescription.class)); 209 | Assert.assertTrue(provider.foundServices.isEmpty()); 210 | } 211 | 212 | @Test 213 | public void testDiscoveryPlayerLostWithEmptyProvider() { 214 | // given 215 | RemoteMediaPlayer remoteMediaPlayer = mockRemoteMediaPlayer(); 216 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 217 | provider.addListener(listener); 218 | 219 | // when 220 | provider.fireTVListener.playerLost(remoteMediaPlayer); 221 | 222 | // then 223 | Mockito.verify(listener, Mockito.times(0)).onServiceRemoved(Mockito.eq(provider), 224 | Mockito.any(ServiceDescription.class)); 225 | Assert.assertTrue(provider.foundServices.isEmpty()); 226 | } 227 | 228 | @Test 229 | public void testDiscoveryPlayerLost() { 230 | // given 231 | RemoteMediaPlayer remoteMediaPlayer = mockRemoteMediaPlayer(); 232 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 233 | provider.addListener(listener); 234 | provider.fireTVListener.playerDiscovered(remoteMediaPlayer); 235 | provider.fireTVListener.playerLost(remoteMediaPlayer); 236 | 237 | ArgumentCaptor argDiscoveryProvider = ArgumentCaptor 238 | .forClass(FireTVDiscoveryProvider.class); 239 | ArgumentCaptor argServiceDescription = ArgumentCaptor 240 | .forClass(ServiceDescription.class); 241 | 242 | // when 243 | Mockito.verify(listener).onServiceRemoved(argDiscoveryProvider.capture(), 244 | argServiceDescription.capture()); 245 | 246 | // then 247 | ServiceDescription serviceDescription = argServiceDescription.getValue(); 248 | Assert.assertSame(provider, argDiscoveryProvider.getValue()); 249 | Assert.assertSame(remoteMediaPlayer, serviceDescription.getDevice()); 250 | Assert.assertTrue(provider.foundServices.isEmpty()); 251 | } 252 | 253 | @Test 254 | public void testDiscoveryNullPlayerLost() { 255 | // given 256 | RemoteMediaPlayer remoteMediaPlayer = null; 257 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 258 | provider.addListener(listener); 259 | 260 | try { 261 | provider.fireTVListener.playerLost(remoteMediaPlayer); 262 | } catch (Exception e) { 263 | Assert.fail("playerLost should not throw exceptions"); 264 | } 265 | 266 | // when 267 | Mockito.verify(listener, Mockito.times(0)).onServiceRemoved( 268 | Mockito.any(DiscoveryProvider.class), 269 | Mockito.any(ServiceDescription.class)); 270 | Assert.assertTrue(provider.foundServices.isEmpty()); 271 | } 272 | 273 | @Test 274 | public void testDiscoveryFailure() { 275 | // given 276 | DiscoveryProviderListener listener = Mockito.mock(DiscoveryProviderListener.class); 277 | provider.addListener(listener); 278 | 279 | // when 280 | provider.fireTVListener.discoveryFailure(); 281 | 282 | // then 283 | Mockito.verify(listener).onServiceDiscoveryFailed(Mockito.eq(provider), 284 | Mockito.any(ServiceCommandError.class)); 285 | } 286 | 287 | @Test 288 | public void testReset() { 289 | // given 290 | provider.fireTVListener.playerDiscovered(mockRemoteMediaPlayer()); 291 | provider.start(); 292 | 293 | // when 294 | provider.reset(); 295 | 296 | // then 297 | Assert.assertTrue(provider.foundServices.isEmpty()); 298 | Mockito.verify(controller).stop(); 299 | } 300 | 301 | @Test 302 | public void testRestart() { 303 | // given 304 | provider.start(); 305 | 306 | // when 307 | provider.restart(); 308 | 309 | // then 310 | Mockito.verify(controller, Mockito.times(2)).start( 311 | Mockito.any(DiscoveryController.IDiscoveryListener.class)); 312 | Mockito.verify(controller, Mockito.times(1)).stop(); 313 | } 314 | 315 | @Test 316 | public void testRescan() { 317 | // given 318 | provider.start(); 319 | 320 | // when 321 | provider.rescan(); 322 | 323 | // then 324 | Mockito.verify(controller, Mockito.times(2)).start( 325 | Mockito.any(DiscoveryController.IDiscoveryListener.class)); 326 | Mockito.verify(controller, Mockito.times(1)).stop(); 327 | } 328 | 329 | @Test 330 | public void testInitialState() { 331 | FireTVDiscoveryProvider provider = new FireTVDiscoveryProvider(Robolectric.application); 332 | Assert.assertNotNull(provider.fireTVListener); 333 | Assert.assertNotNull(provider.foundServices); 334 | Assert.assertNotNull(provider.serviceListeners); 335 | Assert.assertTrue(provider.foundServices.isEmpty()); 336 | Assert.assertTrue(provider.serviceListeners.isEmpty()); 337 | } 338 | 339 | @Test 340 | public void testIsEmptyWithoutFoundServices() { 341 | Assert.assertTrue(provider.isEmpty()); 342 | } 343 | 344 | @Test 345 | public void testIsEmptyWithFoundServices() { 346 | provider.fireTVListener.playerDiscovered(mockRemoteMediaPlayer()); 347 | Assert.assertFalse(provider.isEmpty()); 348 | } 349 | 350 | private RemoteMediaPlayer mockRemoteMediaPlayer() { 351 | RemoteMediaPlayer player = Mockito.mock(RemoteMediaPlayer.class); 352 | Mockito.when(player.getUniqueIdentifier()).thenReturn("UID"); 353 | Mockito.when(player.getName()).thenReturn("FireTVDevice"); 354 | return player; 355 | } 356 | 357 | } 358 | -------------------------------------------------------------------------------- /test/src/com/connectsdk/service/FireTVServiceTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * FireTVServiceTest 3 | * Connect SDK 4 | * 5 | * Copyright (c) 2015 LG Electronics. 6 | * Created by Oleksii Frolov on 08 Jul 2015 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | package com.connectsdk.service; 22 | 23 | import com.amazon.whisperplay.fling.media.controller.RemoteMediaPlayer; 24 | import com.amazon.whisperplay.fling.media.service.CustomMediaPlayer; 25 | import com.amazon.whisperplay.fling.media.service.MediaPlayerInfo; 26 | import com.amazon.whisperplay.fling.media.service.MediaPlayerStatus; 27 | import com.connectsdk.core.ImageInfo; 28 | import com.connectsdk.core.MediaInfo; 29 | import com.connectsdk.core.SubtitleInfo; 30 | import com.connectsdk.device.ConnectableDevice; 31 | import com.connectsdk.discovery.DiscoveryFilter; 32 | import com.connectsdk.service.capability.CapabilityMethods; 33 | import com.connectsdk.service.capability.ExternalInputControl; 34 | import com.connectsdk.service.capability.KeyControl; 35 | import com.connectsdk.service.capability.Launcher; 36 | import com.connectsdk.service.capability.MediaControl; 37 | import com.connectsdk.service.capability.MediaPlayer; 38 | import com.connectsdk.service.capability.MouseControl; 39 | import com.connectsdk.service.capability.PlaylistControl; 40 | import com.connectsdk.service.capability.PowerControl; 41 | import com.connectsdk.service.capability.TVControl; 42 | import com.connectsdk.service.capability.TextInputControl; 43 | import com.connectsdk.service.capability.ToastControl; 44 | import com.connectsdk.service.capability.VolumeControl; 45 | import com.connectsdk.service.capability.WebAppLauncher; 46 | import com.connectsdk.service.capability.listeners.ResponseListener; 47 | import com.connectsdk.service.command.FireTVServiceError; 48 | import com.connectsdk.service.command.NotSupportedServiceCommandError; 49 | import com.connectsdk.service.command.ServiceSubscription; 50 | import com.connectsdk.service.config.ServiceConfig; 51 | import com.connectsdk.service.config.ServiceDescription; 52 | import com.connectsdk.service.sessions.LaunchSession; 53 | 54 | import junit.framework.Assert; 55 | 56 | import org.json.JSONException; 57 | import org.json.JSONObject; 58 | import org.junit.Before; 59 | import org.junit.Test; 60 | import org.junit.runner.RunWith; 61 | import org.mockito.ArgumentCaptor; 62 | import org.mockito.ArgumentMatcher; 63 | import org.mockito.Mockito; 64 | import org.robolectric.RobolectricTestRunner; 65 | import org.robolectric.annotation.Config; 66 | 67 | import java.io.IOException; 68 | import java.util.ArrayList; 69 | import java.util.Arrays; 70 | import java.util.HashSet; 71 | import java.util.List; 72 | import java.util.Set; 73 | import java.util.concurrent.ExecutionException; 74 | import java.util.concurrent.TimeUnit; 75 | import java.util.concurrent.TimeoutException; 76 | 77 | 78 | @RunWith(RobolectricTestRunner.class) 79 | @Config(manifest=Config.NONE) 80 | public class FireTVServiceTest { 81 | 82 | private RemoteMediaPlayer remoteMediaPlayer; 83 | 84 | private FireTVService service; 85 | 86 | @Before 87 | public void setUp() { 88 | ServiceDescription serviceDescription = Mockito.mock(ServiceDescription.class); 89 | ServiceConfig serviceConfig = Mockito.mock(ServiceConfig.class); 90 | remoteMediaPlayer = Mockito.mock(RemoteMediaPlayer.class); 91 | Mockito.when(serviceDescription.getDevice()).thenReturn(remoteMediaPlayer); 92 | service = new FireTVService(serviceDescription, serviceConfig); 93 | } 94 | 95 | @Test 96 | public void testInitialState() { 97 | Assert.assertNotNull(service); 98 | } 99 | 100 | @Test 101 | public void testGetCapabilities() { 102 | Set requiredCapabilities = new HashSet(Arrays.asList(new String[]{ 103 | MediaPlayer.MediaInfo_Get, 104 | MediaPlayer.Display_Image, 105 | MediaPlayer.Play_Audio, 106 | MediaPlayer.Play_Video, 107 | MediaPlayer.Close, 108 | MediaPlayer.MetaData_MimeType, 109 | MediaPlayer.MetaData_Thumbnail, 110 | MediaPlayer.MetaData_Title, 111 | MediaPlayer.Subtitle_WebVTT, 112 | 113 | MediaControl.Play, 114 | MediaControl.Pause, 115 | MediaControl.Stop, 116 | MediaControl.Seek, 117 | MediaControl.Duration, 118 | MediaControl.Position, 119 | MediaControl.PlayState, 120 | MediaControl.PlayState_Subscribe, 121 | })); 122 | Set capabilities = new HashSet(service.getCapabilities()); 123 | Assert.assertEquals(requiredCapabilities, capabilities); 124 | } 125 | 126 | @Test 127 | public void testGetFilter() { 128 | DiscoveryFilter filter = FireTVService.discoveryFilter(); 129 | Assert.assertNotNull(filter); 130 | Assert.assertEquals(FireTVService.ID, filter.getServiceId()); 131 | Assert.assertEquals(FireTVService.ID, filter.getServiceFilter()); 132 | } 133 | 134 | @Test 135 | public void testGetPriorityLevel() { 136 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.HIGH, 137 | service.getPriorityLevel(MediaPlayer.class)); 138 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 139 | service.getPriorityLevel(ExternalInputControl.class)); 140 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 141 | service.getPriorityLevel(KeyControl.class)); 142 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 143 | service.getPriorityLevel(Launcher.class)); 144 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.HIGH, 145 | service.getPriorityLevel(MediaControl.class)); 146 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 147 | service.getPriorityLevel(MouseControl.class)); 148 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 149 | service.getPriorityLevel(PlaylistControl.class)); 150 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 151 | service.getPriorityLevel(PowerControl.class)); 152 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 153 | service.getPriorityLevel(TextInputControl.class)); 154 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 155 | service.getPriorityLevel(ToastControl.class)); 156 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 157 | service.getPriorityLevel(TVControl.class)); 158 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 159 | service.getPriorityLevel(VolumeControl.class)); 160 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 161 | service.getPriorityLevel(WebAppLauncher.class)); 162 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.NOT_SUPPORTED, 163 | service.getPriorityLevel(null)); 164 | } 165 | 166 | 167 | @Test 168 | public void testGetMediaPlayer() { 169 | Assert.assertSame(service, service.getMediaPlayer()); 170 | } 171 | 172 | @Test 173 | public void testGetMediaPlayerCapabilityLevel() { 174 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.HIGH, 175 | service.getMediaPlayerCapabilityLevel()); 176 | } 177 | 178 | @Test 179 | public void testGetMediaInfo() { 180 | MediaPlayer.MediaInfoListener listener = Mockito.mock(MediaPlayer.MediaInfoListener.class); 181 | MediaPlayerInfo info = Mockito.mock(MediaPlayerInfo.class); 182 | Mockito.when(info.getSource()).thenReturn("url"); 183 | Mockito.when(info.getMetadata()).thenReturn( 184 | "{'title':'title','type':'video/mp4','description':'description'," + 185 | "'poster':'poster','noreplay':true}"); 186 | Mockito.when(remoteMediaPlayer.getMediaInfo()) 187 | .thenReturn(new MockAsyncFuture(info)); 188 | service.getMediaInfo(listener); 189 | Mockito.verify(remoteMediaPlayer).getMediaInfo(); 190 | ArgumentCaptor argMediaInfo = ArgumentCaptor.forClass(MediaInfo.class); 191 | Mockito.verify(listener).onSuccess(argMediaInfo.capture()); 192 | MediaInfo metadata = argMediaInfo.getValue(); 193 | Assert.assertEquals("title", metadata.getTitle()); 194 | Assert.assertEquals("description", metadata.getDescription()); 195 | Assert.assertEquals("video/mp4", metadata.getMimeType()); 196 | Assert.assertEquals("poster", metadata.getImages().get(0).getUrl()); 197 | } 198 | 199 | @Test 200 | public void testGetMediaInfoWithException() { 201 | MediaPlayer.MediaInfoListener listener = Mockito.mock(MediaPlayer.MediaInfoListener.class); 202 | Mockito.when(remoteMediaPlayer.getMediaInfo()) 203 | .thenReturn(new MockAsyncFutureFailure()); 204 | service.getMediaInfo(listener); 205 | Mockito.verify(remoteMediaPlayer).getMediaInfo(); 206 | verifyListenerError("Error getting media info", listener); 207 | } 208 | 209 | @Test 210 | public void testGetMediaInfoInWrongState() { 211 | MediaPlayer.MediaInfoListener listener = Mockito.mock(MediaPlayer.MediaInfoListener.class); 212 | Mockito.when(remoteMediaPlayer.getMediaInfo()).thenThrow(IOException.class); 213 | service.getMediaInfo(listener); 214 | Mockito.verify(remoteMediaPlayer).getMediaInfo(); 215 | verifyListenerError("Error getting media info", listener); 216 | } 217 | 218 | @Test 219 | public void testSubscribeMediaInfo() { 220 | MediaPlayer.MediaInfoListener listener = Mockito.mock(MediaPlayer.MediaInfoListener.class); 221 | service.subscribeMediaInfo(listener); 222 | Mockito.verify(listener).onError(Mockito.isA(NotSupportedServiceCommandError.class)); 223 | } 224 | 225 | @Test 226 | public void testDisplayImage() throws JSONException { 227 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 228 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 229 | Mockito.anyBoolean(), Mockito.anyBoolean())) 230 | .thenReturn(new MockAsyncFuture(null)); 231 | service.displayImage("url", "mime", "title", "description", "icon", launchListener); 232 | String metadata = "{'title':'title','description':'description','type':'mime'," + 233 | "'poster':'icon','noreplay':true}"; 234 | verifySetMediaSource("url", metadata, true, false); 235 | verifyLauncherListener(launchListener); 236 | } 237 | 238 | @Test 239 | public void testDisplayImageWithNullMetadataFields() throws JSONException { 240 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 241 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 242 | Mockito.anyBoolean(), Mockito.anyBoolean())) 243 | .thenReturn(new MockAsyncFuture(null)); 244 | service.displayImage("url", "mime", null, null, null, launchListener); 245 | String metadata = "{'type':'mime','noreplay':true}"; 246 | verifySetMediaSource("url", metadata, true, false); 247 | verifyLauncherListener(launchListener); 248 | } 249 | 250 | @Test 251 | public void testDisplayImageWithEmptyMetadataFields() throws JSONException { 252 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 253 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 254 | Mockito.anyBoolean(), Mockito.anyBoolean())) 255 | .thenReturn(new MockAsyncFuture(null)); 256 | service.displayImage("url", "mime", "", "", "", launchListener); 257 | String metadata = "{'type':'mime','noreplay':true}"; 258 | verifySetMediaSource("url", metadata, true, false); 259 | verifyLauncherListener(launchListener); 260 | } 261 | 262 | @Test 263 | public void testDisplayImageWithNullFields() throws JSONException { 264 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 265 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 266 | Mockito.anyBoolean(), Mockito.anyBoolean())) 267 | .thenReturn(new MockAsyncFuture(null)); 268 | service.displayImage(null, null, null, null, null, launchListener); 269 | String metadata = "{'noreplay':true}"; 270 | verifySetMediaSource(null, metadata, true, false); 271 | verifyLauncherListener(launchListener); 272 | } 273 | 274 | @Test 275 | public void testDisplayImageWithMediaInfo() throws JSONException { 276 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 277 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 278 | Mockito.anyBoolean(), Mockito.anyBoolean())) 279 | .thenReturn(new MockAsyncFuture(null)); 280 | MediaInfo mediaInfo = new MediaInfo("url", "mime", "title", "description"); 281 | service.displayImage(mediaInfo, launchListener); 282 | String metadata = "{'title':'title','description':'description','type':'mime'," + 283 | "'noreplay':true}"; 284 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 285 | verifyLauncherListener(launchListener); 286 | } 287 | 288 | @Test 289 | public void testGetMediaControl() { 290 | Assert.assertSame(service, service.getMediaControl()); 291 | } 292 | 293 | @Test 294 | public void testGetMediaControlCapabilityLevel() { 295 | Assert.assertEquals(CapabilityMethods.CapabilityPriorityLevel.HIGH, 296 | service.getMediaControlCapabilityLevel()); 297 | } 298 | 299 | @Test 300 | public void testPlayMedia() throws JSONException { 301 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 302 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 303 | Mockito.anyBoolean(), Mockito.anyBoolean())) 304 | .thenReturn(new MockAsyncFuture(null)); 305 | service.playMedia("url", "mime", "title", "description", "icon", false, launchListener); 306 | String metadata = "{'title':'title','description':'description','type':'mime'," + 307 | "'poster':'icon','noreplay':true}"; 308 | verifySetMediaSource("url", metadata, true, false); 309 | verifyLauncherListener(launchListener); 310 | } 311 | 312 | @Test 313 | public void testPlayMediaWithException() { 314 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 315 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 316 | Mockito.anyBoolean(), Mockito.anyBoolean())) 317 | .thenReturn(new MockAsyncFutureFailure()); 318 | service.playMedia("url", "mime", "title", "desc", "icon", false, launchListener); 319 | verifyListenerError("Error setting media source", launchListener); 320 | } 321 | 322 | @Test 323 | public void testPlayMediaInWrongState() { 324 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 325 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 326 | Mockito.anyBoolean(), Mockito.anyBoolean())) 327 | .thenThrow(IllegalStateException.class); 328 | service.playMedia("url", "mime", "title", "desc", "icon", false, launchListener); 329 | verifyListenerError("Error setting media source", launchListener); 330 | } 331 | 332 | @Test 333 | public void testPlayMediaWithMediaInfo() throws JSONException { 334 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 335 | Mockito.when(remoteMediaPlayer.setMediaSource(Mockito.anyString(), Mockito.anyString(), 336 | Mockito.anyBoolean(), Mockito.anyBoolean())) 337 | .thenReturn(new MockAsyncFuture(null)); 338 | MediaInfo mediaInfo = new MediaInfo("url", "mime", "title", "description"); 339 | service.playMedia(mediaInfo, false, launchListener); 340 | String metadata = "{'title':'title','description':'description','type':'mime'," + 341 | "'noreplay':true}"; 342 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 343 | verifyLauncherListener(launchListener); 344 | } 345 | 346 | @Test 347 | public void testPlayMediaWithMediaInfoWithEmptyImages() throws JSONException { 348 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 349 | MediaInfo mediaInfo = new MediaInfo("url", "mime", "title", "description"); 350 | mediaInfo.setImages(new ArrayList()); 351 | service.playMedia(mediaInfo, false, launchListener); 352 | String metadata = "{'title':'title','description':'description','type':'mime'," + 353 | "'noreplay':true}"; 354 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 355 | } 356 | 357 | @Test 358 | public void testPlayMediaWithMediaInfoWithNullImages() throws JSONException { 359 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 360 | MediaInfo mediaInfo = new MediaInfo("url", "mime", "title", "description"); 361 | mediaInfo.setImages(null); 362 | service.playMedia(mediaInfo, false, launchListener); 363 | String metadata = "{'title':'title','description':'description','type':'mime'," + 364 | "'noreplay':true}"; 365 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 366 | } 367 | 368 | @Test 369 | public void testPlayMediaWithMediaInfoWithNullImage() throws JSONException { 370 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 371 | MediaInfo mediaInfo = new MediaInfo("url", "mime", "title", "description"); 372 | List images = new ArrayList(1); 373 | images.add(null); 374 | mediaInfo.setImages(images); 375 | service.playMedia(mediaInfo, false, launchListener); 376 | String metadata = "{'title':'title','description':'description','type':'mime'," + 377 | "'noreplay':true}"; 378 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 379 | } 380 | 381 | @Test 382 | public void testPlayMediaWithMediaInfoWithImage() throws JSONException { 383 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 384 | MediaInfo mediaInfo = new MediaInfo("url", "mime", "title", "description"); 385 | mediaInfo.addImages(new ImageInfo("imageUrl", ImageInfo.ImageType.Album_Art, 1, 1)); 386 | service.playMedia(mediaInfo, false, launchListener); 387 | String metadata = "{'title':'title','description':'description','type':'mime'," + 388 | "'poster':'imageUrl','noreplay':true}"; 389 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 390 | } 391 | 392 | @Test 393 | public void testPlayMediaWithSubtitles() throws JSONException { 394 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 395 | MediaInfo mediaInfo = new MediaInfo.Builder("url", "mime") 396 | .setTitle("title") 397 | .setDescription("description") 398 | .setIcon("http://icon") 399 | .setSubtitleInfo(new SubtitleInfo.Builder("http://subtitleurl") 400 | .setMimeType("subtitletype") 401 | .setLabel("subtitlelabel") 402 | .setLanguage("en") 403 | .build()) 404 | .build(); 405 | 406 | service.playMedia(mediaInfo, false, launchListener); 407 | String metadata = "{'title':'title','description':'description','type':'mime'," + 408 | "'poster':'http://icon','tracks':[{'srclang':'en','label':'subtitlelabel'," + 409 | "'src':'http://subtitleurl','kind':'subtitles'}],'noreplay':true}"; 410 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 411 | } 412 | 413 | @Test 414 | public void testPlayMediaWithOnlyRequiredFieldsInSubtitles() throws JSONException { 415 | MediaPlayer.LaunchListener launchListener = Mockito.mock(MediaPlayer.LaunchListener.class); 416 | MediaInfo mediaInfo = new MediaInfo.Builder("url", "mime") 417 | .setTitle("title") 418 | .setDescription("description") 419 | .setIcon("http://icon") 420 | .setSubtitleInfo(new SubtitleInfo.Builder("http://subtitleurl").build()) 421 | .build(); 422 | 423 | service.playMedia(mediaInfo, false, launchListener); 424 | String metadata = "{'title':'title','description':'description','type':'mime'," + 425 | "'poster':'http://icon','tracks':[{'srclang':'','label':'','src':'http://subtitleurl'," + 426 | "'kind':'subtitles'}],'noreplay':true}"; 427 | verifySetMediaSource(mediaInfo.getUrl(), metadata, true, false); 428 | } 429 | 430 | @Test 431 | public void testClose() { 432 | LaunchSession launcherSession = Mockito.mock(LaunchSession.class); 433 | ResponseListener listener = Mockito.mock(ResponseListener.class); 434 | service.closeMedia(launcherSession, listener); 435 | Mockito.verify(remoteMediaPlayer).stop(); 436 | } 437 | 438 | @Test 439 | public void testPlay() { 440 | ResponseListener listener = Mockito.mock(ResponseListener.class); 441 | Mockito.when(remoteMediaPlayer.play()).thenReturn(new MockAsyncFuture(null)); 442 | service.play(listener); 443 | Mockito.verify(remoteMediaPlayer).play(); 444 | Mockito.verify(listener).onSuccess(null); 445 | } 446 | 447 | @Test 448 | public void testPlayWithException() { 449 | ResponseListener listener = Mockito.mock(ResponseListener.class); 450 | Mockito.when(remoteMediaPlayer.play()).thenReturn(new MockAsyncFutureFailure()); 451 | service.play(listener); 452 | Mockito.verify(remoteMediaPlayer).play(); 453 | verifyListenerError("Error playing", listener); 454 | } 455 | 456 | @Test 457 | public void testPlayInWrongState() { 458 | Mockito.when(remoteMediaPlayer.play()).thenThrow(IllegalStateException.class); 459 | ResponseListener listener = Mockito.mock(ResponseListener.class); 460 | service.play(listener); 461 | Mockito.verify(remoteMediaPlayer).play(); 462 | verifyListenerError("Error playing", listener); 463 | } 464 | 465 | @Test 466 | public void testPause() { 467 | ResponseListener listener = Mockito.mock(ResponseListener.class); 468 | Mockito.when(remoteMediaPlayer.pause()).thenReturn(new MockAsyncFuture(null)); 469 | service.pause(listener); 470 | Mockito.verify(remoteMediaPlayer).pause(); 471 | Mockito.verify(listener).onSuccess(null); 472 | } 473 | 474 | @Test 475 | public void testPauseWithException() { 476 | ResponseListener listener = Mockito.mock(ResponseListener.class); 477 | Mockito.when(remoteMediaPlayer.pause()).thenReturn(new MockAsyncFutureFailure()); 478 | service.pause(listener); 479 | Mockito.verify(remoteMediaPlayer).pause(); 480 | verifyListenerError("Error pausing", listener); 481 | } 482 | 483 | @Test 484 | public void testPauseInWrongState() { 485 | ResponseListener listener = Mockito.mock(ResponseListener.class); 486 | Mockito.when(remoteMediaPlayer.pause()).thenThrow(IllegalStateException.class); 487 | service.pause(listener); 488 | Mockito.verify(remoteMediaPlayer).pause(); 489 | verifyListenerError("Error pausing", listener); 490 | } 491 | 492 | @Test 493 | public void testStop() { 494 | ResponseListener listener = Mockito.mock(ResponseListener.class); 495 | Mockito.when(remoteMediaPlayer.stop()).thenReturn(new MockAsyncFuture(null)); 496 | service.stop(listener); 497 | Mockito.verify(remoteMediaPlayer).stop(); 498 | Mockito.verify(listener).onSuccess(null); 499 | } 500 | 501 | @Test 502 | public void testStopWithException() { 503 | ResponseListener listener = Mockito.mock(ResponseListener.class); 504 | Mockito.when(remoteMediaPlayer.stop()).thenReturn(new MockAsyncFutureFailure()); 505 | service.stop(listener); 506 | Mockito.verify(remoteMediaPlayer).stop(); 507 | verifyListenerError("Error stopping", listener); 508 | } 509 | 510 | @Test 511 | public void testStopInWrongState() { 512 | ResponseListener listener = Mockito.mock(ResponseListener.class); 513 | Mockito.when(remoteMediaPlayer.stop()).thenThrow(IllegalStateException.class); 514 | service.stop(listener); 515 | Mockito.verify(remoteMediaPlayer).stop(); 516 | verifyListenerError("Error stopping", listener); 517 | } 518 | 519 | @Test 520 | public void testRewind() { 521 | ResponseListener listener = Mockito.mock(ResponseListener.class); 522 | service.rewind(listener); 523 | Mockito.verify(listener).onError(Mockito.isA(NotSupportedServiceCommandError.class)); 524 | } 525 | 526 | @Test 527 | public void testFastForward() { 528 | ResponseListener listener = Mockito.mock(ResponseListener.class); 529 | service.fastForward(listener); 530 | Mockito.verify(listener).onError(Mockito.isA(NotSupportedServiceCommandError.class)); 531 | } 532 | 533 | @Test 534 | public void testPrevious() { 535 | ResponseListener listener = Mockito.mock(ResponseListener.class); 536 | service.previous(listener); 537 | Mockito.verify(listener).onError(Mockito.isA(NotSupportedServiceCommandError.class)); 538 | } 539 | 540 | @Test 541 | public void testNext() { 542 | ResponseListener listener = Mockito.mock(ResponseListener.class); 543 | service.next(listener); 544 | Mockito.verify(listener).onError(Mockito.isA(NotSupportedServiceCommandError.class)); 545 | } 546 | 547 | @Test 548 | public void testSeek() { 549 | ResponseListener listener = Mockito.mock(ResponseListener.class); 550 | Mockito.when(remoteMediaPlayer.seek(CustomMediaPlayer.PlayerSeekMode.Absolute, 777L)) 551 | .thenReturn(new MockAsyncFuture(null)); 552 | service.seek(777L, listener); 553 | Mockito.verify(remoteMediaPlayer).seek( 554 | Mockito.eq(CustomMediaPlayer.PlayerSeekMode.Absolute), Mockito.eq(777L)); 555 | Mockito.verify(listener).onSuccess(null); 556 | } 557 | 558 | @Test 559 | public void testSeekWithException() { 560 | ResponseListener listener = Mockito.mock(ResponseListener.class); 561 | Mockito.when(remoteMediaPlayer.seek(CustomMediaPlayer.PlayerSeekMode.Absolute, 777L)) 562 | .thenReturn(new MockAsyncFutureFailure()); 563 | service.seek(777L, listener); 564 | Mockito.verify(remoteMediaPlayer).seek( 565 | Mockito.eq(CustomMediaPlayer.PlayerSeekMode.Absolute), Mockito.eq(777L)); 566 | verifyListenerError("Error seeking", listener); 567 | } 568 | 569 | @Test 570 | public void testSeekInWrongState() { 571 | ResponseListener listener = Mockito.mock(ResponseListener.class); 572 | Mockito.when(remoteMediaPlayer.seek(CustomMediaPlayer.PlayerSeekMode.Absolute, 777L)) 573 | .thenThrow(IllegalStateException.class); 574 | service.seek(777L, listener); 575 | Mockito.verify(remoteMediaPlayer).seek( 576 | Mockito.eq(CustomMediaPlayer.PlayerSeekMode.Absolute), Mockito.eq(777L)); 577 | verifyListenerError("Error seeking", listener); 578 | } 579 | 580 | @Test 581 | public void testGetDuration() { 582 | MediaControl.DurationListener listener = Mockito.mock(MediaControl.DurationListener.class); 583 | Mockito.when(remoteMediaPlayer.getDuration()).thenReturn(new MockAsyncFuture(123L)); 584 | service.getDuration(listener); 585 | 586 | Mockito.verify(remoteMediaPlayer).getDuration(); 587 | Mockito.verify(listener).onSuccess(Mockito.eq(123L)); 588 | } 589 | 590 | @Test 591 | public void testGetDurationWithException() { 592 | MediaControl.DurationListener listener = Mockito.mock(MediaControl.DurationListener.class); 593 | Mockito.when(remoteMediaPlayer.getDuration()) 594 | .thenReturn(new MockAsyncFutureFailure()); 595 | service.getDuration(listener); 596 | Mockito.verify(remoteMediaPlayer).getDuration(); 597 | verifyListenerError("Error getting duration", listener); 598 | } 599 | 600 | @Test 601 | public void testGetDurationWithInWrongState() { 602 | MediaControl.DurationListener listener = Mockito.mock(MediaControl.DurationListener.class); 603 | Mockito.when(remoteMediaPlayer.getDuration()).thenThrow(IllegalStateException.class); 604 | service.getDuration(listener); 605 | Mockito.verify(remoteMediaPlayer).getDuration(); 606 | verifyListenerError("Error getting duration", listener); 607 | } 608 | 609 | @Test 610 | public void testGetPosition() { 611 | MediaControl.PositionListener listener = Mockito.mock(MediaControl.PositionListener.class); 612 | Mockito.when(remoteMediaPlayer.getPosition()).thenReturn(new MockAsyncFuture(123L)); 613 | service.getPosition(listener); 614 | Mockito.verify(remoteMediaPlayer).getPosition(); 615 | Mockito.verify(listener).onSuccess(Mockito.eq(123L)); 616 | } 617 | 618 | @Test 619 | public void testGetPositionWithException() { 620 | MediaControl.PositionListener listener = Mockito.mock(MediaControl.PositionListener.class); 621 | Mockito.when(remoteMediaPlayer.getPosition()) 622 | .thenReturn(new MockAsyncFutureFailure()); 623 | service.getPosition(listener); 624 | Mockito.verify(remoteMediaPlayer).getPosition(); 625 | verifyListenerError("Error getting position", listener); 626 | } 627 | 628 | @Test 629 | public void testGetPositionInWrongState() { 630 | MediaControl.PositionListener listener = Mockito.mock(MediaControl.PositionListener.class); 631 | Mockito.when(remoteMediaPlayer.getPosition()).thenThrow(IllegalStateException.class); 632 | service.getPosition(listener); 633 | Mockito.verify(remoteMediaPlayer).getPosition(); 634 | verifyListenerError("Error getting position", listener); 635 | } 636 | 637 | @Test 638 | public void testGetPlayState() { 639 | MediaControl.PlayStateListener listener = 640 | Mockito.mock(MediaControl.PlayStateListener.class); 641 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 642 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Paused); 643 | Mockito.when(remoteMediaPlayer.getStatus()) 644 | .thenReturn(new MockAsyncFuture(status)); 645 | service.getPlayState(listener); 646 | Mockito.verify(remoteMediaPlayer).getStatus(); 647 | Mockito.verify(listener).onSuccess(Mockito.eq(MediaControl.PlayStateStatus.Paused)); 648 | } 649 | 650 | @Test 651 | public void testGetPlayStateWithException() { 652 | MediaControl.PlayStateListener listener = Mockito 653 | .mock(MediaControl.PlayStateListener.class); 654 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 655 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Paused); 656 | Mockito.when(remoteMediaPlayer.getStatus()) 657 | .thenReturn(new MockAsyncFutureFailure()); 658 | service.getPlayState(listener); 659 | Mockito.verify(remoteMediaPlayer).getStatus(); 660 | verifyListenerError("Error getting play state", listener); 661 | } 662 | 663 | @Test 664 | public void testGetPlayStateInWrongState() { 665 | MediaControl.PlayStateListener listener = Mockito 666 | .mock(MediaControl.PlayStateListener.class); 667 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 668 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Paused); 669 | Mockito.when(remoteMediaPlayer.getStatus()).thenThrow(IllegalStateException.class); 670 | service.getPlayState(listener); 671 | Mockito.verify(remoteMediaPlayer).getStatus(); 672 | verifyListenerError("Error getting play state", listener); 673 | } 674 | 675 | @Test 676 | public void testSubscribePlayState() { 677 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 678 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Paused); 679 | Mockito.when(remoteMediaPlayer.getStatus()) 680 | .thenReturn(new MockAsyncFuture(status)); 681 | 682 | MediaControl.PlayStateListener listener = 683 | Mockito.mock(MediaControl.PlayStateListener.class); 684 | service.subscribePlayState(listener); 685 | Mockito.verify(remoteMediaPlayer).addStatusListener( 686 | Mockito.any(CustomMediaPlayer.StatusListener.class)); 687 | Mockito.verify(listener).onSuccess(Mockito.eq(MediaControl.PlayStateStatus.Paused)); 688 | } 689 | 690 | @Test 691 | public void testSubscribePlayStateShouldBeSingle() { 692 | MediaControl.PlayStateListener listener = 693 | Mockito.mock(MediaControl.PlayStateListener.class); 694 | ServiceSubscription subscription = 695 | service.subscribePlayState(listener); 696 | ServiceSubscription subscription2 = 697 | service.subscribePlayState(listener); 698 | Assert.assertSame(subscription, subscription2); 699 | } 700 | 701 | @Test 702 | public void testSubscribePlayStateShouldAddNewListener() { 703 | MediaControl.PlayStateListener listenerFirst = 704 | Mockito.mock(MediaControl.PlayStateListener.class); 705 | MediaControl.PlayStateListener listenerSecond = 706 | Mockito.mock(MediaControl.PlayStateListener.class); 707 | 708 | ServiceSubscription subscription = 709 | service.subscribePlayState(listenerFirst); 710 | Assert.assertEquals(1, subscription.getListeners().size()); 711 | 712 | ServiceSubscription subscription2 = 713 | service.subscribePlayState(listenerSecond); 714 | Assert.assertEquals(2, subscription2.getListeners().size()); 715 | 716 | ServiceSubscription subscription3 = 717 | service.subscribePlayState(listenerSecond); 718 | Assert.assertEquals(2, subscription2.getListeners().size()); 719 | } 720 | 721 | @Test 722 | public void testDisconnectWithPlayStateSubscription() { 723 | MediaControl.PlayStateListener listener = 724 | Mockito.mock(MediaControl.PlayStateListener.class); 725 | CustomMediaPlayer.StatusListener subscription = 726 | (CustomMediaPlayer.StatusListener)service.subscribePlayState(listener); 727 | service.disconnect(); 728 | Mockito.verify(remoteMediaPlayer).removeStatusListener(subscription); 729 | } 730 | 731 | @Test 732 | public void testDisconnectWithoutSubscription() { 733 | service.disconnect(); 734 | Mockito.verify(remoteMediaPlayer, Mockito.times(0)).removeStatusListener( 735 | Mockito.any(CustomMediaPlayer.StatusListener.class)); 736 | } 737 | 738 | @Test 739 | public void testCreatePlayStateStatusFromFireTVStatusPaused() { 740 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 741 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Paused); 742 | Assert.assertEquals(MediaControl.PlayStateStatus.Paused, 743 | service.createPlayStateStatusFromFireTVStatus(status)); 744 | } 745 | 746 | @Test 747 | public void testCreatePlayStateStatusFromFireTVStatusError() { 748 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 749 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Error); 750 | Assert.assertEquals(MediaControl.PlayStateStatus.Unknown, 751 | service.createPlayStateStatusFromFireTVStatus(status)); 752 | } 753 | 754 | @Test 755 | public void testCreatePlayStateStatusFromFireTVStatusFinished() { 756 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 757 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Finished); 758 | Assert.assertEquals(MediaControl.PlayStateStatus.Finished, 759 | service.createPlayStateStatusFromFireTVStatus(status)); 760 | } 761 | 762 | @Test 763 | public void testCreatePlayStateStatusFromFireTVStatusNoSource() { 764 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 765 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.NoSource); 766 | Assert.assertEquals(MediaControl.PlayStateStatus.Idle, 767 | service.createPlayStateStatusFromFireTVStatus(status)); 768 | } 769 | 770 | @Test 771 | public void testCreatePlayStateStatusFromFireTVStatusPlaying() { 772 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 773 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Playing); 774 | Assert.assertEquals(MediaControl.PlayStateStatus.Playing, 775 | service.createPlayStateStatusFromFireTVStatus(status)); 776 | } 777 | 778 | @Test 779 | public void testCreatePlayStateStatusFromFireTVStatusPreparing() { 780 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 781 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.PreparingMedia); 782 | Assert.assertEquals(MediaControl.PlayStateStatus.Buffering, 783 | service.createPlayStateStatusFromFireTVStatus(status)); 784 | } 785 | 786 | @Test 787 | public void testCreatePlayStateStatusFromFireTVStatusReady() { 788 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 789 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.ReadyToPlay); 790 | Assert.assertEquals(MediaControl.PlayStateStatus.Unknown, 791 | service.createPlayStateStatusFromFireTVStatus(status)); 792 | } 793 | 794 | @Test 795 | public void testCreatePlayStateStatusFromFireTVStatusSeeking() { 796 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 797 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Seeking); 798 | Assert.assertEquals(MediaControl.PlayStateStatus.Unknown, 799 | service.createPlayStateStatusFromFireTVStatus(status)); 800 | } 801 | 802 | @Test 803 | public void testConnect() { 804 | ConnectableDevice listener = Mockito.mock(ConnectableDevice.class); 805 | service.setListener(listener); 806 | 807 | service.connect(); 808 | 809 | Mockito.verify(listener).onConnectionSuccess(service); 810 | Assert.assertTrue(service.isConnected()); 811 | } 812 | 813 | @Test 814 | public void testConnectWithNullRemoteMediaPlayer() { 815 | ServiceDescription serviceDescription = Mockito.mock(ServiceDescription.class); 816 | ServiceConfig serviceConfig = Mockito.mock(ServiceConfig.class); 817 | FireTVService service = new FireTVService(serviceDescription, serviceConfig); 818 | 819 | ConnectableDevice listener = Mockito.mock(ConnectableDevice.class); 820 | service.setListener(listener); 821 | 822 | service.connect(); 823 | 824 | Mockito.verify(listener, Mockito.times(0)).onConnectionSuccess(service); 825 | Assert.assertFalse(service.isConnected()); 826 | } 827 | 828 | @Test 829 | public void testIsConnectable() { 830 | Assert.assertTrue(service.isConnectable()); 831 | } 832 | 833 | @Test 834 | public void testSubscribePlayStateWithNullListenerShouldNotCrash() { 835 | try { 836 | FireTVService.PlayStateSubscription subscription = 837 | (FireTVService.PlayStateSubscription) service.subscribePlayState(null); 838 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 839 | Mockito.when(status.getState()).thenReturn(MediaPlayerStatus.MediaState.Playing); 840 | subscription.onStatusChange(status, 0); 841 | } catch (RuntimeException e) { 842 | Assert.fail("subscribePlayState(null) should not throw a runtime exception"); 843 | } 844 | } 845 | 846 | @Test 847 | public void testSubscribePlayStateWithNullStatusShouldReturnUnknownState() { 848 | MediaControl.PlayStateListener listener = 849 | Mockito.mock(MediaControl.PlayStateListener.class); 850 | FireTVService.PlayStateSubscription subscription = 851 | (FireTVService.PlayStateSubscription) service.subscribePlayState(listener); 852 | subscription.onStatusChange(null, 0); 853 | Mockito.verify(listener).onSuccess(MediaControl.PlayStateStatus.Unknown); 854 | } 855 | 856 | @Test 857 | public void testSubscribeMediaInfoShouldReturnNull() { 858 | Assert.assertNull(service.subscribeMediaInfo(null)); 859 | } 860 | 861 | private void verifySetMediaSource(String source, String meta, boolean isAutoPlay, 862 | boolean isPlayInBg) throws JSONException { 863 | ArgumentCaptor argSource = ArgumentCaptor.forClass(String.class); 864 | ArgumentCaptor argMeta = ArgumentCaptor.forClass(String.class); 865 | ArgumentCaptor argAutoPlay = ArgumentCaptor.forClass(Boolean.class); 866 | ArgumentCaptor argPlayInBg = ArgumentCaptor.forClass(Boolean.class); 867 | Mockito.verify(remoteMediaPlayer).setMediaSource(argSource.capture(), argMeta.capture(), 868 | argAutoPlay.capture(), argPlayInBg.capture()); 869 | 870 | Assert.assertEquals(source, argSource.getValue()); 871 | Assert.assertEquals(new JSONObject(meta).toString(), 872 | new JSONObject(argMeta.getValue()).toString()); 873 | Assert.assertEquals(isAutoPlay, argAutoPlay.getValue().booleanValue()); 874 | Assert.assertEquals(isPlayInBg, argPlayInBg.getValue().booleanValue()); 875 | } 876 | 877 | private void verifyLauncherListener(MediaPlayer.LaunchListener launchListener) { 878 | ArgumentCaptor argMediaObject = ArgumentCaptor 879 | .forClass(MediaPlayer.MediaLaunchObject.class); 880 | Mockito.verify(launchListener).onSuccess(argMediaObject.capture()); 881 | MediaPlayer.MediaLaunchObject mediaLaunchObject = argMediaObject.getValue(); 882 | Assert.assertSame(service, mediaLaunchObject.mediaControl); 883 | Assert.assertEquals(null, mediaLaunchObject.playlistControl); 884 | Assert.assertEquals(LaunchSession.LaunchSessionType.Media, 885 | mediaLaunchObject.launchSession.getSessionType()); 886 | Assert.assertSame(service, mediaLaunchObject.launchSession.getService()); 887 | } 888 | 889 | private void verifyListenerError(String errorMessage, ResponseListener listener) { 890 | ArgumentCaptor error = ArgumentCaptor 891 | .forClass(FireTVServiceError.class); 892 | Mockito.verify(listener).onError(error.capture()); 893 | Assert.assertEquals(errorMessage, error.getValue().getMessage()); 894 | } 895 | 896 | static class MockAsyncFuture implements RemoteMediaPlayer.AsyncFuture { 897 | 898 | private T value; 899 | 900 | public MockAsyncFuture(T value) { 901 | this.value = value; 902 | } 903 | 904 | @Override 905 | public void getAsync(RemoteMediaPlayer.FutureListener futureListener) { 906 | futureListener.futureIsNow(this); 907 | } 908 | 909 | @Override 910 | public boolean cancel(boolean mayInterruptIfRunning) { 911 | return false; 912 | } 913 | 914 | @Override 915 | public boolean isCancelled() { 916 | return false; 917 | } 918 | 919 | @Override 920 | public boolean isDone() { 921 | return true; 922 | } 923 | 924 | @Override 925 | public T get() throws InterruptedException, ExecutionException { 926 | return value; 927 | } 928 | 929 | @Override 930 | public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, 931 | TimeoutException { 932 | return value; 933 | } 934 | } 935 | 936 | static class MockAsyncFutureFailure implements RemoteMediaPlayer.AsyncFuture { 937 | 938 | @Override 939 | public void getAsync(RemoteMediaPlayer.FutureListener futureListener) { 940 | futureListener.futureIsNow(this); 941 | } 942 | 943 | @Override 944 | public boolean cancel(boolean mayInterruptIfRunning) { 945 | return false; 946 | } 947 | 948 | @Override 949 | public boolean isCancelled() { 950 | return false; 951 | } 952 | 953 | @Override 954 | public boolean isDone() { 955 | return true; 956 | } 957 | 958 | @Override 959 | public T get() throws InterruptedException, ExecutionException { 960 | throw new ExecutionException("Operation error", 961 | new Exception("MockAsyncFutureFailure")); 962 | } 963 | 964 | @Override 965 | public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, 966 | TimeoutException { 967 | throw new ExecutionException("Operation error", 968 | new Exception("MockAsyncFutureFailure")); 969 | } 970 | } 971 | 972 | static class DoubleMatcher extends ArgumentMatcher { 973 | 974 | private double expected; 975 | private double delta; 976 | 977 | public DoubleMatcher(double expected, double delta) { 978 | this.expected = expected; 979 | this.delta = delta; 980 | } 981 | 982 | @Override 983 | public boolean matches(final Object actual) { 984 | return Math.abs(expected - (Double) actual) <= delta; 985 | } 986 | } 987 | 988 | static class FloatMatcher extends ArgumentMatcher { 989 | 990 | private float expected; 991 | private float delta; 992 | 993 | public FloatMatcher(float expected, float delta) { 994 | this.expected = expected; 995 | this.delta = delta; 996 | } 997 | 998 | @Override 999 | public boolean matches(final Object actual) { 1000 | return Math.abs(expected - (Float) actual) <= delta; 1001 | } 1002 | } 1003 | 1004 | } 1005 | -------------------------------------------------------------------------------- /test/src/com/connectsdk/service/PlayStateSubscriptionParameterizedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * PlayStateSubscriptionParameterizedTest 3 | * Connect SDK 4 | * 5 | * Copyright (c) 2015 LG Electronics. 6 | * Created by Oleksii Frolov on 08 Jul 2015 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | package com.connectsdk.service; 21 | 22 | 23 | import com.amazon.whisperplay.fling.media.controller.RemoteMediaPlayer; 24 | import com.amazon.whisperplay.fling.media.service.MediaPlayerStatus; 25 | import com.connectsdk.service.capability.MediaControl; 26 | import com.connectsdk.service.config.ServiceConfig; 27 | import com.connectsdk.service.config.ServiceDescription; 28 | 29 | import org.junit.Test; 30 | import org.junit.runner.RunWith; 31 | import org.mockito.Mockito; 32 | import org.robolectric.ParameterizedRobolectricTestRunner; 33 | import org.robolectric.annotation.Config; 34 | 35 | import java.util.Arrays; 36 | import java.util.Collection; 37 | import java.util.List; 38 | 39 | @RunWith(ParameterizedRobolectricTestRunner.class) 40 | @Config(manifest=Config.NONE) 41 | public class PlayStateSubscriptionParameterizedTest { 42 | 43 | private final MediaControl.PlayStateStatus sdkMediaState; 44 | 45 | private MediaControl.PlayStateStatus anotherSdkMediaState; 46 | 47 | private final MediaPlayerStatus.MediaState fireTVMediaState; 48 | 49 | private final MediaPlayerStatus.MediaState anotherFireTVMediaState; 50 | 51 | private final RemoteMediaPlayer remoteMediaPlayer; 52 | 53 | private final FireTVService service; 54 | 55 | private final MediaControl.PlayStateListener listener; 56 | 57 | private final FireTVService.PlayStateSubscription subscription; 58 | 59 | static List mediaStateValues = 60 | Arrays.asList(new MediaPlayerStatus.MediaState[] { 61 | MediaPlayerStatus.MediaState.NoSource, 62 | MediaPlayerStatus.MediaState.Error, 63 | MediaPlayerStatus.MediaState.Finished, 64 | MediaPlayerStatus.MediaState.Paused, 65 | MediaPlayerStatus.MediaState.Playing, 66 | MediaPlayerStatus.MediaState.PreparingMedia, 67 | MediaPlayerStatus.MediaState.ReadyToPlay, 68 | MediaPlayerStatus.MediaState.Seeking, 69 | }); 70 | 71 | static List anotherMediaStateValues = 72 | Arrays.asList(new MediaPlayerStatus.MediaState[] { 73 | MediaPlayerStatus.MediaState.Error, 74 | MediaPlayerStatus.MediaState.NoSource, 75 | MediaPlayerStatus.MediaState.NoSource, 76 | MediaPlayerStatus.MediaState.NoSource, 77 | MediaPlayerStatus.MediaState.NoSource, 78 | MediaPlayerStatus.MediaState.NoSource, 79 | MediaPlayerStatus.MediaState.NoSource, 80 | MediaPlayerStatus.MediaState.NoSource, 81 | }); 82 | 83 | static List playStateValues = 84 | Arrays.asList(new MediaControl.PlayStateStatus[] { 85 | MediaControl.PlayStateStatus.Idle, 86 | MediaControl.PlayStateStatus.Unknown, 87 | MediaControl.PlayStateStatus.Finished, 88 | MediaControl.PlayStateStatus.Paused, 89 | MediaControl.PlayStateStatus.Playing, 90 | MediaControl.PlayStateStatus.Buffering, 91 | MediaControl.PlayStateStatus.Unknown, 92 | MediaControl.PlayStateStatus.Unknown, 93 | }); 94 | 95 | static List anotherPlayStateValues = 96 | Arrays.asList(new MediaControl.PlayStateStatus[] { 97 | MediaControl.PlayStateStatus.Unknown, 98 | MediaControl.PlayStateStatus.Idle, 99 | MediaControl.PlayStateStatus.Idle, 100 | MediaControl.PlayStateStatus.Idle, 101 | MediaControl.PlayStateStatus.Idle, 102 | MediaControl.PlayStateStatus.Idle, 103 | MediaControl.PlayStateStatus.Idle, 104 | MediaControl.PlayStateStatus.Idle, 105 | }); 106 | 107 | // we use indexes here instead of real argument because of ParameterizedRobolectricTestRunner 108 | // limitations for enum types 109 | @ParameterizedRobolectricTestRunner.Parameters 110 | public static Collection data() { 111 | return Arrays.asList(new Object[][] { 112 | {0},{1},{2},{3},{4},{5},{6},{7}, 113 | }); 114 | } 115 | 116 | public PlayStateSubscriptionParameterizedTest(int index) { 117 | this.fireTVMediaState = mediaStateValues.get(index); 118 | this.anotherFireTVMediaState = anotherMediaStateValues.get(index); 119 | this.sdkMediaState = playStateValues.get(index); 120 | this.anotherSdkMediaState = anotherPlayStateValues.get(index); 121 | 122 | ServiceDescription serviceDescription = Mockito.mock(ServiceDescription.class); 123 | ServiceConfig serviceConfig = Mockito.mock(ServiceConfig.class); 124 | remoteMediaPlayer = Mockito.mock(RemoteMediaPlayer.class); 125 | Mockito.when(serviceDescription.getDevice()).thenReturn(remoteMediaPlayer); 126 | service = new FireTVService(serviceDescription, serviceConfig); 127 | listener = Mockito.mock(MediaControl.PlayStateListener.class); 128 | subscription = service.new PlayStateSubscription(listener); 129 | } 130 | 131 | @Test 132 | public void testStatusChange() { 133 | // given 134 | MediaControl.PlayStateListener secondListener = 135 | Mockito.mock(MediaControl.PlayStateListener.class); 136 | subscription.listeners.add(secondListener); 137 | 138 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 139 | Mockito.when(status.getState()).thenReturn(fireTVMediaState); 140 | 141 | // when 142 | subscription.onStatusChange(status, 10); 143 | 144 | // then 145 | Mockito.verify(listener).onSuccess(sdkMediaState); 146 | Mockito.verify(secondListener).onSuccess(sdkMediaState); 147 | } 148 | 149 | @Test 150 | public void testStatusChangeRepeat() { 151 | // given 152 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 153 | Mockito.when(status.getState()).thenReturn(fireTVMediaState); 154 | 155 | // when 156 | subscription.onStatusChange(status, 10); 157 | subscription.onStatusChange(status, 20); 158 | 159 | // then 160 | Mockito.verify(listener, Mockito.times(1)).onSuccess(sdkMediaState); 161 | } 162 | 163 | @Test 164 | public void testStatusChangeRepeatThreeTimes() { 165 | MediaPlayerStatus status = Mockito.mock(MediaPlayerStatus.class); 166 | Mockito.when(status.getState()).thenReturn(fireTVMediaState); 167 | MediaPlayerStatus anotherStatus = Mockito.mock(MediaPlayerStatus.class); 168 | Mockito.when(anotherStatus.getState()).thenReturn(anotherFireTVMediaState); 169 | 170 | 171 | subscription.onStatusChange(status, 10); 172 | subscription.onStatusChange(status, 20); 173 | Mockito.verify(listener, Mockito.times(1)).onSuccess(sdkMediaState); 174 | 175 | subscription.onStatusChange(anotherStatus, 20); 176 | Mockito.verify(listener, Mockito.times(1)).onSuccess(anotherSdkMediaState); 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /test/src/com/connectsdk/service/PlayStateSubscriptionTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * PlayStateSubscriptionTest 3 | * Connect SDK 4 | * 5 | * Copyright (c) 2015 LG Electronics. 6 | * Created by Oleksii Frolov on 08 Jul 2015 7 | * 8 | * Licensed under the Apache License, Version 2.0 (the "License"); 9 | * you may not use this file except in compliance with the License. 10 | * You may obtain a copy of the License at 11 | * 12 | * http://www.apache.org/licenses/LICENSE-2.0 13 | * 14 | * Unless required by applicable law or agreed to in writing, software 15 | * distributed under the License is distributed on an "AS IS" BASIS, 16 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 | * See the License for the specific language governing permissions and 18 | * limitations under the License. 19 | */ 20 | 21 | package com.connectsdk.service; 22 | 23 | import com.amazon.whisperplay.fling.media.controller.RemoteMediaPlayer; 24 | import com.connectsdk.service.capability.MediaControl; 25 | import com.connectsdk.service.config.ServiceConfig; 26 | import com.connectsdk.service.config.ServiceDescription; 27 | 28 | import junit.framework.Assert; 29 | 30 | import org.junit.Before; 31 | import org.junit.Test; 32 | import org.junit.runner.RunWith; 33 | import org.mockito.Mockito; 34 | import org.robolectric.RobolectricTestRunner; 35 | import org.robolectric.annotation.Config; 36 | 37 | 38 | 39 | @RunWith(RobolectricTestRunner.class) 40 | @Config(manifest=Config.NONE) 41 | public class PlayStateSubscriptionTest { 42 | 43 | private RemoteMediaPlayer remoteMediaPlayer; 44 | 45 | private FireTVService service; 46 | 47 | private FireTVService.PlayStateSubscription subscription; 48 | 49 | private MediaControl.PlayStateListener listener; 50 | 51 | @Before 52 | public void setUp() { 53 | ServiceDescription serviceDescription = Mockito.mock(ServiceDescription.class); 54 | ServiceConfig serviceConfig = Mockito.mock(ServiceConfig.class); 55 | remoteMediaPlayer = Mockito.mock(RemoteMediaPlayer.class); 56 | Mockito.when(serviceDescription.getDevice()).thenReturn(remoteMediaPlayer); 57 | service = new FireTVService(serviceDescription, serviceConfig); 58 | listener = Mockito.mock(MediaControl.PlayStateListener.class); 59 | subscription = service.new PlayStateSubscription(listener); 60 | } 61 | 62 | @Test 63 | public void testInitialState() { 64 | Assert.assertEquals(1, subscription.listeners.size()); 65 | Assert.assertTrue(subscription.listeners.contains(listener)); 66 | } 67 | 68 | @Test 69 | public void testAddListener() { 70 | subscription.addListener(Mockito.mock(MediaControl.PlayStateListener.class)); 71 | Assert.assertEquals(2, subscription.listeners.size()); 72 | } 73 | 74 | @Test 75 | public void testRemoveListener() { 76 | subscription.removeListener(listener); 77 | Assert.assertTrue(subscription.listeners.isEmpty()); 78 | } 79 | 80 | @Test 81 | public void testUnsubscribe() { 82 | subscription.unsubscribe(); 83 | 84 | Mockito.verify(remoteMediaPlayer).removeStatusListener(subscription); 85 | } 86 | 87 | } 88 | --------------------------------------------------------------------------------