/* * WebOSWebAppSession * Connect SDK * * Copyright (c) 2014 LG Electronics. * Created by Jeffrey Glenn on 07 Mar 2014 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.connectsdk.service.sessions; import android.util.Log; import java.util.Enumeration; import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import org.json.JSONException; import org.json.JSONObject; import com.connectsdk.core.ImageInfo; import com.connectsdk.core.MediaInfo; import com.connectsdk.core.Util; import com.connectsdk.service.DeviceService; import com.connectsdk.service.DeviceService.PairingType; import com.connectsdk.service.WebOSTVService; import com.connectsdk.service.capability.MediaControl; import com.connectsdk.service.capability.MediaPlayer; import com.connectsdk.service.capability.PlaylistControl; import com.connectsdk.service.capability.listeners.ResponseListener; import com.connectsdk.service.command.ServiceCommand; import com.connectsdk.service.command.ServiceCommandError; import com.connectsdk.service.command.ServiceSubscription; import com.connectsdk.service.command.URLServiceSubscription; import com.connectsdk.service.sessions.LaunchSession.LaunchSessionType; import com.connectsdk.service.webos.WebOSTVServiceSocketClient; import com.connectsdk.service.webos.WebOSTVServiceSocketClient.WebOSTVServiceSocketClientListener; public class WebOSWebAppSession extends WebAppSession { private static final String namespaceKey = "connectsdk."; protected WebOSTVService service; ResponseListener<ServiceCommand<ResponseListener<Object>>> mConnectionListener; public WebOSTVServiceSocketClient socket; public URLServiceSubscription<ResponseListener<Object>> appToAppSubscription; private ServiceSubscription<PlayStateListener> mPlayStateSubscription; private ServiceSubscription<MessageListener> mMessageSubscription; private ConcurrentHashMap<String, ServiceCommand<?>> mActiveCommands; private ServiceSubscription<WebAppPinStatusListener> mWebAppPinnedSubscription; String mFullAppId; private int UID; private boolean connected; public WebOSWebAppSession(LaunchSession launchSession, DeviceService service) { super(launchSession, service); UID = 0; mActiveCommands = new ConcurrentHashMap<String, ServiceCommand<?>>(0, 0.75f, 10); connected = false; this.service = (WebOSTVService) service; } private int getNextId() { return ++UID; } public Boolean isConnected() { return connected; } public void setConnected(Boolean connected) { this.connected = connected; } public void handleMediaEvent(JSONObject payload) { String type = payload.optString("type"); if (type.length() == 0) { String errorMsg = payload.optString("error"); if (errorMsg.length() == 0) { return; } else { Log.w(Util.T, "Play State Error: " + errorMsg); if (mPlayStateSubscription != null) { for (PlayStateListener listener : mPlayStateSubscription.getListeners()) { Util.postError(listener, new ServiceCommandError(errorMsg)); } } } } if (type.equals("playState")) { if (mPlayStateSubscription == null) return; String playStateString = payload.optString(type); if (playStateString.length() == 0) return; final MediaControl.PlayStateStatus playState = parsePlayState(playStateString); for (PlayStateListener listener : mPlayStateSubscription.getListeners()) { Util.postSuccess(listener, playState); } } } public String getFullAppId() { if (mFullAppId == null) { if (launchSession.getSessionType() != LaunchSessionType.WebApp) mFullAppId = launchSession.getAppId(); else { Enumeration<String> enumeration = service.getWebAppIdMappings().keys(); while (enumeration.hasMoreElements()) { String mappedFullAppId = enumeration.nextElement(); String mappedAppId = service.getWebAppIdMappings().get(mappedFullAppId); if (mappedAppId.equalsIgnoreCase(launchSession.getAppId())) { mFullAppId = mappedAppId; break; } } } } if (mFullAppId == null) return launchSession.getAppId(); else return mFullAppId; } public void setFullAppId(String fullAppId) { mFullAppId = fullAppId; } private WebOSTVServiceSocketClientListener mSocketListener = new WebOSTVServiceSocketClientListener() { @Override public void onRegistrationFailed(ServiceCommandError error) { } @Override public Boolean onReceiveMessage(JSONObject payload) { String type = payload.optString("type"); if ("p2p".equals(type)) { String fromAppId = payload.optString("from"); if (!fromAppId.equalsIgnoreCase(getFullAppId())) return false; Object message = payload.opt("payload"); if (message instanceof JSONObject) { JSONObject messageJSON = (JSONObject) message; String contentType = messageJSON.optString("contentType"); Integer contentTypeIndex = contentType.indexOf("connectsdk."); if (contentType != null && contentTypeIndex >= 0) { String payloadKey = contentType.split("connectsdk.")[1]; if (payloadKey == null || payloadKey.length() == 0) return false; JSONObject messagePayload = messageJSON.optJSONObject(payloadKey); if (payloadKey.equalsIgnoreCase("media-error")) { handleMediaEvent(messageJSON); return false; } if (messagePayload == null) return false; if (payloadKey.equalsIgnoreCase("mediaEvent")) handleMediaEvent(messagePayload); else if (payloadKey.equalsIgnoreCase("mediaCommandResponse")) handleMediaCommandResponse(messagePayload); } else { handleMessage(messageJSON); } } else if (message instanceof String) { handleMessage(message); } return false; } return true; } @Override public void onFailWithError(ServiceCommandError error) { connected = false; appToAppSubscription = null; if (mConnectionListener != null) { if (error == null) { error = new ServiceCommandError(0, "Unknown error connecting to web socket", null); } mConnectionListener.onError(error); } mConnectionListener = null; } @Override public void onConnect() { if (mConnectionListener != null) mConnectionListener.onSuccess(null); mConnectionListener = null; } @Override public void onCloseWithError(ServiceCommandError error) { connected = false; appToAppSubscription = null; if (mConnectionListener != null) { if (error != null) mConnectionListener.onError(error); else { if (getWebAppSessionListener() != null) { getWebAppSessionListener() .onWebAppSessionDisconnect(WebOSWebAppSession.this); } } } mConnectionListener = null; } @Override public void onBeforeRegister(PairingType pairingType) { } }; @SuppressWarnings("unchecked") public void handleMediaCommandResponse(final JSONObject payload) { String requestID = payload.optString("requestId"); if (requestID.length() == 0) return; final ServiceCommand<ResponseListener<Object>> command = (ServiceCommand<ResponseListener<Object>>) mActiveCommands.get(requestID); if (command == null) return; String mError = payload.optString("error"); if (mError.length() != 0) { Util.postError(command.getResponseListener(), new ServiceCommandError(0, mError, null)); } else { Util.postSuccess(command.getResponseListener(), payload); } mActiveCommands.remove(requestID); } public void handleMessage(final Object message) { Util.runOnUI(new Runnable() { @Override public void run() { if (getWebAppSessionListener() != null) getWebAppSessionListener().onReceiveMessage(WebOSWebAppSession.this, message); } }); } public PlayStateStatus parsePlayState(String playStateString) { if (playStateString.equals("playing")) return PlayStateStatus.Playing; else if (playStateString.equals("paused")) return PlayStateStatus.Paused; else if (playStateString.equals("idle")) return PlayStateStatus.Idle; else if (playStateString.equals("buffering")) return PlayStateStatus.Buffering; else if (playStateString.equals("finished")) return PlayStateStatus.Finished; return PlayStateStatus.Unknown; } public void connect(ResponseListener<Object> connectionListener) { connect(false, connectionListener); } @Override public void join(ResponseListener<Object> connectionListener) { connect(true, connectionListener); } private void connect(final Boolean joinOnly, final ResponseListener<Object> connectionListener) { if (socket != null && socket.getState() == WebOSTVServiceSocketClient.State.CONNECTING) { if (connectionListener != null) { connectionListener.onError(new ServiceCommandError( 0, "You have a connection request pending, please wait until it has finished", null)); } return; } if (isConnected()) { if (connectionListener != null) connectionListener.onSuccess(null); return; } mConnectionListener = new ResponseListener<ServiceCommand<ResponseListener<Object>>>() { @Override public void onError(ServiceCommandError error) { if (socket != null) disconnectFromWebApp(); if (connectionListener != null) { if (error == null) { error = new ServiceCommandError(0, "Unknown error connecting to web app", null); } connectionListener.onError(error); } } @Override public void onSuccess(ServiceCommand<ResponseListener<Object>> object) { ResponseListener<Object> finalConnectionListener = new ResponseListener<Object>() { @Override public void onError(ServiceCommandError error) { disconnectFromWebApp(); if (connectionListener != null) connectionListener.onError(error); } @Override public void onSuccess(Object object) { connected = true; if (connectionListener != null) connectionListener.onSuccess(object); } }; service.connectToWebApp(WebOSWebAppSession.this, joinOnly, finalConnectionListener); } }; if (socket != null) { if (socket.isConnected()) mConnectionListener.onSuccess(null); else socket.connect(); } else { socket = new WebOSTVServiceSocketClient(service, WebOSTVServiceSocketClient .getURI(service)); socket.setListener(mSocketListener); socket.connect(); } } public void disconnectFromWebApp() { connected = false; mConnectionListener = null; if (appToAppSubscription != null) { appToAppSubscription.removeListeners(); appToAppSubscription = null; } if (socket != null) { socket.setListener(null); socket.disconnect(); socket = null; } } @Override public void sendMessage(final String message, final ResponseListener<Object> listener) { if (message == null || message.length() == 0) { Util.postError(listener, new ServiceCommandError(0, "Cannot send an Empty Message", null)); return; } sendP2PMessage(message, listener); } @Override public void sendMessage(final JSONObject message, final ResponseListener<Object> listener) { if (message == null || message.length() == 0) { Util.postError(listener, new ServiceCommandError(0, "Cannot send an Empty Message", null)); return; } sendP2PMessage(message, listener); } private void sendP2PMessage(final Object message, final ResponseListener<Object> listener) { JSONObject _payload = new JSONObject(); try { _payload.put("type", "p2p"); _payload.put("to", getFullAppId()); _payload.put("payload", message); } catch (JSONException ex) { // do nothing } if (isConnected()) { socket.sendMessage(_payload, null); Util.postSuccess(listener, null); } else { ResponseListener<Object> connectListener = new ResponseListener<Object>() { @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } @Override public void onSuccess(Object object) { sendP2PMessage(message, listener); } }; connect(connectListener); } } @Override public void close(ResponseListener<Object> listener) { mActiveCommands.clear(); if (mPlayStateSubscription != null) { mPlayStateSubscription.unsubscribe(); mPlayStateSubscription = null; } if (mMessageSubscription != null) { mMessageSubscription.unsubscribe(); mMessageSubscription = null; } if (mWebAppPinnedSubscription != null) { mWebAppPinnedSubscription.unsubscribe(); mWebAppPinnedSubscription = null; } service.getWebAppLauncher().closeWebApp(launchSession, listener); } @Override public void pinWebApp(String webAppId, ResponseListener<Object> listener) { service.getWebAppLauncher().pinWebApp(webAppId, listener); } @Override public void unPinWebApp(String webAppId, ResponseListener<Object> listener) { service.getWebAppLauncher().unPinWebApp(webAppId, listener); } @Override public void isWebAppPinned(String webAppId, WebAppPinStatusListener listener) { service.getWebAppLauncher().isWebAppPinned(webAppId, listener); } @Override public ServiceSubscription<WebAppPinStatusListener> subscribeIsWebAppPinned( String webAppId, WebAppPinStatusListener listener) { mWebAppPinnedSubscription = service.getWebAppLauncher().subscribeIsWebAppPinned(webAppId, listener); return mWebAppPinnedSubscription; } @Override public void seek(final long position, ResponseListener<Object> listener) { if (position < 0) { Util.postError(listener, new ServiceCommandError(0, "Must pass a valid positive value", null)); return; } int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { put("contentType", namespaceKey + "mediaCommand"); put("mediaCommand", new JSONObject() { { put("type", "seek"); put("position", position / 1000); put("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); } ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>( null, null, null, listener); mActiveCommands.put(requestId, command); sendMessage(message, listener); } @Override public void getPosition(final PositionListener listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { put("contentType", namespaceKey + "mediaCommand"); put("mediaCommand", new JSONObject() { { put("type", "getPosition"); put("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); } ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>( null, null, null, new ResponseListener<Object>() { @Override public void onSuccess(Object response) { try { long position = ((JSONObject) response).getLong("position"); Util.postSuccess(listener, position * 1000); } catch (JSONException e) { this.onError(new ServiceCommandError(0, "JSON Parse error", null)); } } @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } }); mActiveCommands.put(requestId, command); sendMessage(message, new ResponseListener<Object>() { @Override public void onSuccess(Object response) { } @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } }); } @Override public void getDuration(final DurationListener listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { put("contentType", namespaceKey + "mediaCommand"); put("mediaCommand", new JSONObject() { { put("type", "getDuration"); put("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); } ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>( null, null, null, new ResponseListener<Object>() { @Override public void onSuccess(Object response) { try { long position = ((JSONObject) response).getLong("duration"); Util.postSuccess(listener, position * 1000); } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); } } @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } }); mActiveCommands.put(requestId, command); sendMessage(message, new ResponseListener<Object>() { @Override public void onSuccess(Object response) { } @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } }); } @Override public void getPlayState(final PlayStateListener listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { put("contentType", namespaceKey + "mediaCommand"); put("mediaCommand", new JSONObject() { { put("type", "getPlayState"); put("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); } ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>( null, null, null, new ResponseListener<Object>() { @Override public void onSuccess(Object response) { try { String playStateString = ((JSONObject) response) .getString("playState"); PlayStateStatus playState = parsePlayState(playStateString); Util.postSuccess(listener, playState); } catch (JSONException e) { this.onError(new ServiceCommandError(0, "JSON Parse error", null)); } } @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } }); mActiveCommands.put(requestId, command); sendMessage(message, new ResponseListener<Object>() { @Override public void onSuccess(Object response) { } @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } }); } @Override public ServiceSubscription<PlayStateListener> subscribePlayState( final PlayStateListener listener) { if (mPlayStateSubscription == null) mPlayStateSubscription = new URLServiceSubscription<MediaControl.PlayStateListener>( null, null, null, null); if (!connected) { connect(new ResponseListener<Object>() { @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } @Override public void onSuccess(Object response) { } }); } if (!mPlayStateSubscription.getListeners().contains(listener)) mPlayStateSubscription.addListener(listener); return mPlayStateSubscription; } /***************** * Media Control * *****************/ @Override public MediaControl getMediaControl() { return this; } @Override public CapabilityPriorityLevel getMediaControlCapabilityLevel() { return CapabilityPriorityLevel.HIGH; } /**************** * Media Player * ****************/ @Override public MediaPlayer getMediaPlayer() { return this; } @Override public CapabilityPriorityLevel getMediaPlayerCapabilityLevel() { return CapabilityPriorityLevel.HIGH; } @Override public void displayImage(final String url, final String mimeType, final String title, final String description, final String iconSrc, final MediaPlayer.LaunchListener listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { putOpt("contentType", namespaceKey + "mediaCommand"); putOpt("mediaCommand", new JSONObject() { { putOpt("type", "displayImage"); putOpt("mediaURL", url); putOpt("iconURL", iconSrc); putOpt("title", title); putOpt("description", description); putOpt("mimeType", mimeType); putOpt("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); return; } ResponseListener<Object> response = new ResponseListener<Object>() { @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } @Override public void onSuccess(Object object) { Util.postSuccess(listener, new MediaLaunchObject(launchSession, getMediaControl())); } }; ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>( socket, null, null, response); mActiveCommands.put(requestId, command); sendP2PMessage(message, new ResponseListener<Object>() { @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } @Override public void onSuccess(Object object) { } }); } @Override public void displayImage(MediaInfo mediaInfo, MediaPlayer.LaunchListener listener) { String mediaUrl = null; String mimeType = null; String title = null; String desc = null; String iconSrc = null; if (mediaInfo != null) { mediaUrl = mediaInfo.getUrl(); mimeType = mediaInfo.getMimeType(); title = mediaInfo.getTitle(); desc = mediaInfo.getDescription(); if (mediaInfo.getImages() != null && mediaInfo.getImages().size() > 0) { ImageInfo imageInfo = mediaInfo.getImages().get(0); iconSrc = imageInfo.getUrl(); } } displayImage(mediaUrl, mimeType, title, desc, iconSrc, listener); } @Override public void playMedia(final String url, final String mimeType, final String title, final String description, final String iconSrc, final boolean shouldLoop, final MediaPlayer.LaunchListener listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { putOpt("contentType", namespaceKey + "mediaCommand"); putOpt("mediaCommand", new JSONObject() { { putOpt("type", "playMedia"); putOpt("mediaURL", url); putOpt("iconURL", iconSrc); putOpt("title", title); putOpt("description", description); putOpt("mimeType", mimeType); putOpt("shouldLoop", shouldLoop); putOpt("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); return; } ResponseListener<Object> response = new ResponseListener<Object>() { @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } @Override public void onSuccess(Object object) { Util.postSuccess(listener, new MediaLaunchObject(launchSession, getMediaControl(), getPlaylistControl())); } }; ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>(null, null, null, response); mActiveCommands.put(requestId, command); sendMessage(message, new ResponseListener<Object>() { @Override public void onError(ServiceCommandError error) { Util.postError(listener, error); } @Override public void onSuccess(Object object) { } }); } @Override public void playMedia(final MediaInfo mediaInfo, final boolean shouldLoop, final MediaPlayer.LaunchListener listener) { String mediaUrl = null; String mimeType = null; String title = null; String desc = null; String iconSrc = null; if (mediaInfo != null) { mediaUrl = mediaInfo.getUrl(); mimeType = mediaInfo.getMimeType(); title = mediaInfo.getTitle(); desc = mediaInfo.getDescription(); if (mediaInfo.getImages() != null && mediaInfo.getImages().size() > 0) { ImageInfo imageInfo = mediaInfo.getImages().get(0); iconSrc = imageInfo.getUrl(); } } playMedia(mediaUrl, mimeType, title, desc, iconSrc, shouldLoop, listener); } /**************** * Playlist Control * ****************/ public PlaylistControl getPlaylistControl() { return this; } @Override public CapabilityPriorityLevel getPlaylistControlCapabilityLevel() { return CapabilityPriorityLevel.HIGH; } @Override public void jumpToTrack(final long index, final ResponseListener<Object> listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { put("contentType", namespaceKey + "mediaCommand"); put("mediaCommand", new JSONObject() { { put("type", "jumpToTrack"); put("requestId", requestId); put("index", (int)index); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); return; } ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>(null, null, null, listener); mActiveCommands.put(requestId, command); sendMessage(message, listener); } @Override public void previous(final ResponseListener<Object> listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { put("contentType", namespaceKey + "mediaCommand"); put("mediaCommand", new JSONObject() { { put("type", "playPrevious"); put("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); return; } ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>(null, null, null, listener); mActiveCommands.put(requestId, command); sendMessage(message, listener); } @Override public void next(final ResponseListener<Object> listener) { int requestIdNumber = getNextId(); final String requestId = String.format(Locale.US, "req%d", requestIdNumber); JSONObject message = null; try { message = new JSONObject() { { put("contentType", namespaceKey + "mediaCommand"); put("mediaCommand", new JSONObject() { { put("type", "playNext"); put("requestId", requestId); } }); } }; } catch (JSONException e) { Util.postError(listener, new ServiceCommandError(0, "JSON Parse error", null)); return; } ServiceCommand<ResponseListener<Object>> command = new ServiceCommand<ResponseListener<Object>>(null, null, null, listener); mActiveCommands.put(requestId, command); sendMessage(message, listener); } }