PageRenderTime 153ms CodeModel.GetById 30ms RepoModel.GetById 1ms app.codeStats 0ms

/Squeezer/src/main/java/uk/org/ngo/squeezer/service/ConnectionState.java

https://github.com/kaaholst/android-squeezer
Java | 291 lines | 193 code | 51 blank | 47 comment | 33 complexity | 04fe11770c98fe1f4134afb70db361ec MD5 | raw file
  1. /*
  2. * Copyright (c) 2009 Google Inc. All Rights Reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package uk.org.ngo.squeezer.service;
  17. import android.os.SystemClock;
  18. import android.text.TextUtils;
  19. import android.util.Log;
  20. import androidx.annotation.IntDef;
  21. import androidx.annotation.NonNull;
  22. import java.lang.annotation.Retention;
  23. import java.lang.annotation.RetentionPolicy;
  24. import java.util.ArrayList;
  25. import java.util.HashSet;
  26. import java.util.List;
  27. import java.util.HashMap;
  28. import java.util.Map;
  29. import java.util.Set;
  30. import java.util.concurrent.ConcurrentHashMap;
  31. import java.util.concurrent.atomic.AtomicReference;
  32. import de.greenrobot.event.EventBus;
  33. import uk.org.ngo.squeezer.Util;
  34. import uk.org.ngo.squeezer.model.CustomJiveItemHandling;
  35. import uk.org.ngo.squeezer.model.MenuStatusMessage;
  36. import uk.org.ngo.squeezer.model.Player;
  37. import uk.org.ngo.squeezer.model.PlayerState;
  38. import uk.org.ngo.squeezer.service.event.ActivePlayerChanged;
  39. import uk.org.ngo.squeezer.service.event.ConnectionChanged;
  40. import uk.org.ngo.squeezer.service.event.HandshakeComplete;
  41. import uk.org.ngo.squeezer.service.event.PlayersChanged;
  42. public class ConnectionState {
  43. private static final String TAG = "ConnectionState";
  44. ConnectionState(@NonNull EventBus eventBus) {
  45. mEventBus = eventBus;
  46. mHomeMenuHandling = new HomeMenuHandling(eventBus);
  47. }
  48. private final EventBus mEventBus;
  49. private final HomeMenuHandling mHomeMenuHandling;
  50. private final Map<Player, RandomPlay> mRandomPlay = new HashMap<>();
  51. public final static String MEDIA_DIRS = "mediadirs";
  52. // Connection state machine
  53. @IntDef({MANUAL_DISCONNECT, DISCONNECTED, CONNECTION_STARTED, CONNECTION_FAILED, CONNECTION_COMPLETED})
  54. @Retention(RetentionPolicy.SOURCE)
  55. public @interface ConnectionStates {}
  56. /** User disconnected */
  57. public static final int MANUAL_DISCONNECT = 0;
  58. /** Ordinarily disconnected from the server. */
  59. public static final int DISCONNECTED = 1;
  60. /** A connection has been started. */
  61. public static final int CONNECTION_STARTED = 2;
  62. /** The connection to the server did not complete. */
  63. public static final int CONNECTION_FAILED = 3;
  64. /** The connection to the server completed, the handshake can start. */
  65. public static final int CONNECTION_COMPLETED = 4;
  66. @ConnectionStates
  67. private volatile int mConnectionState = DISCONNECTED;
  68. /** Milliseconds since boot of latest auto connect */
  69. private volatile long autoConnect;
  70. /** Minimum milliseconds between automatic connection */
  71. private static final long AUTO_CONNECT_INTERVAL = 60_000;
  72. /** Map Player IDs to the {@link uk.org.ngo.squeezer.model.Player} with that ID. */
  73. private final Map<String, Player> mPlayers = new ConcurrentHashMap<>();
  74. /** The active player (the player to which commands are sent by default). */
  75. private final AtomicReference<Player> mActivePlayer = new AtomicReference<>();
  76. private final AtomicReference<String> serverVersion = new AtomicReference<>();
  77. private final AtomicReference<String[]> mediaDirs = new AtomicReference<>();
  78. public boolean canAutoConnect() {
  79. return (mConnectionState == DISCONNECTED || mConnectionState == CONNECTION_FAILED)
  80. && ((SystemClock.elapsedRealtime() - autoConnect) > AUTO_CONNECT_INTERVAL);
  81. }
  82. public void setAutoConnect() {
  83. this.autoConnect = SystemClock.elapsedRealtime();
  84. }
  85. /**
  86. * Sets a new connection state, and posts a sticky
  87. * {@link uk.org.ngo.squeezer.service.event.ConnectionChanged} event with the new state.
  88. *
  89. * @param connectionState The new connection state.
  90. */
  91. void setConnectionState(@ConnectionStates int connectionState) {
  92. Log.i(TAG, "setConnectionState(" + mConnectionState + " => " + connectionState + ")");
  93. updateConnectionState(connectionState);
  94. mEventBus.postSticky(new ConnectionChanged(connectionState));
  95. }
  96. void setConnectionError(ConnectionError connectionError) {
  97. Log.i(TAG, "setConnectionError(" + mConnectionState + " => " + connectionError.name() + ")");
  98. updateConnectionState(CONNECTION_FAILED);
  99. mEventBus.postSticky(new ConnectionChanged(connectionError));
  100. }
  101. private void updateConnectionState(@ConnectionStates int connectionState) {
  102. // Clear data if we were previously connected
  103. if (isConnected() && !isConnected(connectionState)) {
  104. mEventBus.removeAllStickyEvents();
  105. setServerVersion(null);
  106. mPlayers.clear();
  107. setActivePlayer(null);
  108. }
  109. mConnectionState = connectionState;
  110. }
  111. public void setPlayers(Map<String, Player> players) {
  112. mPlayers.clear();
  113. mPlayers.putAll(players);
  114. mEventBus.postSticky(new PlayersChanged());
  115. }
  116. Player getPlayer(String playerId) {
  117. if (playerId == null) return null;
  118. return mPlayers.get(playerId);
  119. }
  120. public Map<String, Player> getPlayers() {
  121. return mPlayers;
  122. }
  123. public Player getActivePlayer() {
  124. return mActivePlayer.get();
  125. }
  126. @NonNull Set<Player> getSyncGroup() {
  127. Set<Player> out = new HashSet<>();
  128. Player player = getActivePlayer();
  129. if (player != null) {
  130. out.add(player);
  131. Player master = getPlayer(player.getPlayerState().getSyncMaster());
  132. if (master != null) out.add(master);
  133. for (String slave : player.getPlayerState().getSyncSlaves()) {
  134. Player syncSlave = getPlayer(slave);
  135. if (syncSlave != null) out.add(syncSlave);
  136. }
  137. }
  138. return out;
  139. }
  140. @NonNull Set<Player> getVolumeSyncGroup(boolean groupVolume) {
  141. Player player = getActivePlayer();
  142. if (player != null && (player.isSyncVolume() || !groupVolume)) {
  143. Set<Player> players = new HashSet<>();
  144. players.add(player);
  145. return players;
  146. }
  147. return getSyncGroup();
  148. }
  149. public @NonNull ISqueezeService.VolumeInfo getVolume(boolean groupVolume) {
  150. Set<Player> syncGroup = getVolumeSyncGroup(groupVolume);
  151. int lowestVolume = 100;
  152. int higestVolume = 0;
  153. boolean muted = false;
  154. List<String> playerNames = new ArrayList<>();
  155. for (Player player : syncGroup) {
  156. int currentVolume = player.getPlayerState().getCurrentVolume();
  157. if (currentVolume < lowestVolume) lowestVolume = currentVolume;
  158. if (currentVolume > higestVolume) higestVolume = currentVolume;
  159. muted |= player.getPlayerState().isMuted();
  160. playerNames.add(player.getName());
  161. }
  162. long volume = Math.round(lowestVolume / (100.0 - (higestVolume - lowestVolume)) * 100);
  163. return new ISqueezeService.VolumeInfo(muted, (int) volume, TextUtils.join(", ", playerNames));
  164. }
  165. void setActivePlayer(Player player) {
  166. mActivePlayer.set(player);
  167. mEventBus.post(new ActivePlayerChanged(player));
  168. }
  169. void setServerVersion(String version) {
  170. if (Util.atomicReferenceUpdated(serverVersion, version)) {
  171. if (version != null && mConnectionState == CONNECTION_COMPLETED) {
  172. HandshakeComplete event = new HandshakeComplete(getServerVersion());
  173. Log.i(TAG, "Handshake complete: " + event);
  174. mEventBus.postSticky(event);
  175. }
  176. }
  177. }
  178. void setMediaDirs(String[] mediaDirs) {
  179. this.mediaDirs.set(mediaDirs);
  180. }
  181. public HomeMenuHandling getHomeMenuHandling() {
  182. return mHomeMenuHandling;
  183. }
  184. public RandomPlay getRandomPlay(Player player) {
  185. RandomPlay randomPlay = mRandomPlay.get(player);
  186. if (randomPlay != null) {
  187. return mRandomPlay.get(player);
  188. } else {
  189. RandomPlay newRandomPlay = new RandomPlay(player);
  190. mRandomPlay.put(player, newRandomPlay);
  191. return newRandomPlay;
  192. }
  193. }
  194. // For menu updates sent from LMS, handling of archived nodes needs testing!
  195. void menuStatusEvent(MenuStatusMessage event) {
  196. if (event.playerId.equals(getActivePlayer().getId())) {
  197. mHomeMenuHandling.handleMenuStatusEvent(event);
  198. }
  199. }
  200. String getServerVersion() {
  201. return serverVersion.get();
  202. }
  203. String[] getMediaDirs() {
  204. return mediaDirs.get();
  205. }
  206. /**
  207. * @return True if the socket connection to the server has completed.
  208. */
  209. boolean isConnected() {
  210. return isConnected(mConnectionState);
  211. }
  212. /**
  213. * @return True if the socket connection to the server has completed.
  214. */
  215. static boolean isConnected(@ConnectionStates int connectionState) {
  216. return connectionState == CONNECTION_COMPLETED;
  217. }
  218. /**
  219. * @return True if the socket connection to the server has started, but not yet
  220. * completed (successfully or unsuccessfully).
  221. */
  222. boolean isConnectInProgress() {
  223. return isConnectInProgress(mConnectionState);
  224. }
  225. /**
  226. * @return True if the socket connection to the server has started, but not yet
  227. * completed (successfully or unsuccessfully).
  228. */
  229. static boolean isConnectInProgress(@ConnectionStates int connectionState) {
  230. return connectionState == CONNECTION_STARTED;
  231. }
  232. @NonNull
  233. @Override
  234. public String toString() {
  235. return "ConnectionState{" +
  236. "mConnectionState=" + mConnectionState +
  237. ", serverVersion=" + serverVersion +
  238. '}';
  239. }
  240. }