summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBilal Alsharifi <bilal.alsharifi@gmail.com>2019-03-04 13:03:37 -0500
committerBilal Alsharifi <bilal.alsharifi@gmail.com>2019-03-04 13:03:37 -0500
commit252153e2183184bfce7bfb392c42f2e71eae5a8b (patch)
tree6c7b72951a3f73a2aa5535f83a9d8807888a54e0
parent5340706d01b3f83849430ae967ae92ccba7bbdad (diff)
downloadsdl_android-252153e2183184bfce7bfb392c42f2e71eae5a8b.tar.gz
Add base dir
-rw-r--r--base/src/main/java/android/os/AsyncTask.java8
-rw-r--r--base/src/main/java/android/os/Parcel.java40
-rw-r--r--base/src/main/java/android/os/Parcelable.java14
-rw-r--r--base/src/main/java/android/util/Log.java44
-rw-r--r--base/src/main/java/com/smartdevicelink/Dispatcher/IDispatchingStrategy.java9
-rw-r--r--base/src/main/java/com/smartdevicelink/Dispatcher/ProxyMessageDispatcher.java63
-rw-r--r--base/src/main/java/com/smartdevicelink/SdlConnection/ISdlConnectionListener.java38
-rw-r--r--base/src/main/java/com/smartdevicelink/SdlConnection/SdlSession.java748
-rw-r--r--base/src/main/java/com/smartdevicelink/exception/SdlException.java47
-rw-r--r--base/src/main/java/com/smartdevicelink/exception/SdlExceptionCause.java25
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java4
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/BaseSdlManagerListener.java4
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/BaseSubManager.java83
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/CompletionListener.java10
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/StreamingStateMachine.java72
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/audio/BaseAudioStreamManager.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java339
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/file/MultipleFileCompletionListener.java14
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java6
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java237
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java146
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/permission/BasePermissionManager.java350
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/permission/OnPermissionChangeListener.java20
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/permission/PermissionElement.java42
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/permission/PermissionFilter.java70
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/permission/PermissionStatus.java68
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java358
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java568
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java888
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java272
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonState.java97
-rw-r--r--base/src/main/java/com/smartdevicelink/managers/video/BaseVideoStreamManager.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/marshal/JsonRPCMarshaller.java127
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/BinaryFrameHeader.java118
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/IProtocolListener.java44
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/ISdlProtocol.java56
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/ISecondaryTransportListener.java8
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java151
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SdlPacket.java422
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SdlPacketFactory.java97
-rw-r--r--base/src/main/java/com/smartdevicelink/protocol/SdlProtocol.java1432
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/IProxyListener.java19
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCMessage.java138
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCNotification.java29
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCRequest.java50
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java997
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCResponse.java183
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCStreamController.java32
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java322
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/SdlProxyConfigurationResources.java34
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/SystemCapabilityManager.java250
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/TTSChunkFactory.java39
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/callbacks/InternalProxyMessage.java19
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/callbacks/OnError.java25
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyClosed.java33
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyOpened.java8
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceEnded.java21
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceNACKed.java21
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/constants/Jingles.java15
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/constants/Names.java506
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/IAudioStreamListener.java71
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerALM.java48
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerBase.java359
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/IPutFileResponseListener.java9
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java229
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdlServiceListener.java11
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/IVideoStreamListener.java90
-rw-r--r--base/src/main/java/com/smartdevicelink/proxy/interfaces/OnSystemCapabilityListener.java6
-rw-r--r--base/src/main/java/com/smartdevicelink/security/BaseSdlSecurityBase.java91
-rw-r--r--base/src/main/java/com/smartdevicelink/security/ISecurityInitializedListener.java5
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/AbstractPacketizer.java82
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/IStreamListener.java7
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java246
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/StreamRPCPacketizer.java308
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingCodec.java49
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingLPCMParams.java64
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingParams.java55
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java503
-rw-r--r--base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java218
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/DiagLevel.java61
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/ISTListener.java5
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/Mime.java100
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/OpenRPCMessage.java86
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/SdlTrace.java465
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/enums/DetailLevel.java16
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/enums/InterfaceActivityDirection.java15
-rw-r--r--base/src/main/java/com/smartdevicelink/trace/enums/Mod.java18
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/BaseTransportConfig.java35
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/ITransportListener.java17
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/IpcSocketServer.java63
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/SdlPsm.java261
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/SdlTransport.java122
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/SiphonServer.java384
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/TransportConstants.java263
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/TransportManager.java269
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/WebSocketServer2.java157
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/WebSocketServerConfig.java25
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/enums/TransportType.java33
-rw-r--r--base/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java126
-rw-r--r--base/src/main/java/com/smartdevicelink/util/BitConverter.java116
-rw-r--r--base/src/main/java/com/smartdevicelink/util/ByteEnumer.java60
-rw-r--r--base/src/main/java/com/smartdevicelink/util/CorrelationIdGenerator.java25
-rw-r--r--base/src/main/java/com/smartdevicelink/util/DebugTool.java354
-rw-r--r--base/src/main/java/com/smartdevicelink/util/FileUtls.java41
-rw-r--r--base/src/main/java/com/smartdevicelink/util/HttpRequestTask.java180
-rw-r--r--base/src/main/java/com/smartdevicelink/util/IConsole.java10
-rw-r--r--base/src/main/java/com/smartdevicelink/util/NativeLogTool.java112
-rw-r--r--base/src/main/java/com/smartdevicelink/util/SdlDataTypeConverter.java63
-rw-r--r--base/src/main/java/com/smartdevicelink/util/Version.java75
-rw-r--r--base/src/main/java/org/json/JSON.java116
-rw-r--r--base/src/main/java/org/json/JSONArray.java626
-rw-r--r--base/src/main/java/org/json/JSONException.java58
-rw-r--r--base/src/main/java/org/json/JSONObject.java831
-rw-r--r--base/src/main/java/org/json/JSONStringer.java432
-rw-r--r--base/src/main/java/org/json/JSONTokener.java611
115 files changed, 18584 insertions, 0 deletions
diff --git a/base/src/main/java/android/os/AsyncTask.java b/base/src/main/java/android/os/AsyncTask.java
new file mode 100644
index 000000000..c5ee88d4b
--- /dev/null
+++ b/base/src/main/java/android/os/AsyncTask.java
@@ -0,0 +1,8 @@
+package android.os;
+
+public abstract class AsyncTask<Params, Progress, Result> {
+
+
+ abstract protected Result doInBackground(Params...params);
+
+}
diff --git a/base/src/main/java/android/os/Parcel.java b/base/src/main/java/android/os/Parcel.java
new file mode 100644
index 000000000..11a93ef79
--- /dev/null
+++ b/base/src/main/java/android/os/Parcel.java
@@ -0,0 +1,40 @@
+package android.os;
+
+public class Parcel {
+
+ public void writeInt(int data){
+
+ }
+
+ public void writeByteArray(byte[] bytes){
+
+ }
+
+ public void writeString(String data){
+
+ }
+
+ public void writeParcelable(Parcelable p, int flags){
+
+ }
+
+ public int readInt(){
+ return 0;
+ }
+
+ public String readString(){
+ return "hello";
+ }
+
+ public byte[] readByteArray(byte[] array){
+ return array;
+ }
+
+ public Parcelable readParcelable(ClassLoader loader){
+ return null;
+ }
+
+ public int dataAvail(){
+ return 0;
+ }
+}
diff --git a/base/src/main/java/android/os/Parcelable.java b/base/src/main/java/android/os/Parcelable.java
new file mode 100644
index 000000000..c648a2718
--- /dev/null
+++ b/base/src/main/java/android/os/Parcelable.java
@@ -0,0 +1,14 @@
+package android.os;
+
+
+public interface Parcelable {
+
+ int describeContents();
+ void writeToParcel(Parcel dest, int flags);
+
+
+ abstract class Creator<T>{
+ public abstract T[] newArray(int size);
+ }
+
+}
diff --git a/base/src/main/java/android/util/Log.java b/base/src/main/java/android/util/Log.java
new file mode 100644
index 000000000..501567255
--- /dev/null
+++ b/base/src/main/java/android/util/Log.java
@@ -0,0 +1,44 @@
+package android.util;
+
+public class Log {
+
+
+ public static int i(String tag, String message){
+ System.out.print("\r\nINFO: " + tag+ " - " + message);
+ return 10;
+
+
+ }
+ public static int v(String tag, String message){
+ System.out.print("\r\nVERBOSE: " + tag+ " - " + message);
+ return 10;
+
+
+ }
+ public static int d(String tag, String message){
+ System.out.print("\r\nDEBUG: " + tag+ " - " + message);
+ return 10;
+
+
+ }
+ public static int w(String tag, String message){
+ System.out.print("\r\nWARN: " + tag+ " - " + message);
+ return 10;
+
+
+ }
+ public static int e(String tag, String message){
+ System.out.print("\r\nERROR: " + tag+ " - " + message);
+ return 10;
+
+ }
+ public static int e(String tag, String message, Exception e){
+ System.out.print("\r\nERROR: " + tag+ " - " + message + " - " + e.getMessage());
+ return 10;
+ }
+ public static int e(String tag, String message, Throwable t){
+ System.out.print("\r\nERROR: " + tag+ " - " + message + " - " + t.getMessage());
+ return 10;
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/Dispatcher/IDispatchingStrategy.java b/base/src/main/java/com/smartdevicelink/Dispatcher/IDispatchingStrategy.java
new file mode 100644
index 000000000..4ed8851ea
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/Dispatcher/IDispatchingStrategy.java
@@ -0,0 +1,9 @@
+package com.smartdevicelink.Dispatcher;
+
+public interface IDispatchingStrategy<T> {
+ public void dispatch(T message);
+
+ public void handleDispatchingError(String info, Exception ex);
+
+ public void handleQueueingError(String info, Exception ex);
+}
diff --git a/base/src/main/java/com/smartdevicelink/Dispatcher/ProxyMessageDispatcher.java b/base/src/main/java/com/smartdevicelink/Dispatcher/ProxyMessageDispatcher.java
new file mode 100644
index 000000000..e27f4bf8f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/Dispatcher/ProxyMessageDispatcher.java
@@ -0,0 +1,63 @@
+package com.smartdevicelink.Dispatcher;
+
+import java.util.concurrent.LinkedBlockingQueue;
+
+import com.smartdevicelink.util.DebugTool;
+
+public class ProxyMessageDispatcher<T> {
+ LinkedBlockingQueue<T> _queue = null;
+ private Thread _messageDispatchingThread = null;
+ IDispatchingStrategy<T> _strategy = null;
+
+ // Boolean to track if disposed
+ private Boolean dispatcherDisposed = false;
+
+ public ProxyMessageDispatcher(String THREAD_NAME, IDispatchingStrategy<T> strategy) {
+ _queue = new LinkedBlockingQueue<T>();
+
+ _strategy = strategy;
+
+ // Create dispatching thread
+ _messageDispatchingThread = new Thread(new Runnable() {public void run(){handleMessages();}});
+ _messageDispatchingThread.setName(THREAD_NAME);
+ _messageDispatchingThread.setDaemon(true);
+ _messageDispatchingThread.start();
+ }
+
+ public void dispose() {
+ dispatcherDisposed = true;
+
+ if(_messageDispatchingThread != null) {
+ _messageDispatchingThread.interrupt();
+ _messageDispatchingThread = null;
+ }
+ }
+
+ private void handleMessages() {
+
+ try {
+ T thisMessage;
+
+ while(dispatcherDisposed == false) {
+ thisMessage = _queue.take();
+ _strategy.dispatch(thisMessage);
+ }
+ } catch (InterruptedException e) {
+ // Thread was interrupted by dispose() method, no action required
+ return;
+ } catch (Exception e) {
+ DebugTool.logError("Error occurred dispating message.", e);
+ _strategy.handleDispatchingError("Error occurred dispating message.", e);
+ }
+ }
+
+ public void queueMessage(T message) {
+ try {
+ _queue.put(message);
+ } catch(ClassCastException e) {
+ _strategy.handleQueueingError("ClassCastException encountered when queueing message.", e);
+ } catch(Exception e) {
+ _strategy.handleQueueingError("Exception encountered when queueing message.", e);
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/SdlConnection/ISdlConnectionListener.java b/base/src/main/java/com/smartdevicelink/SdlConnection/ISdlConnectionListener.java
new file mode 100644
index 000000000..34d4295b6
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/SdlConnection/ISdlConnectionListener.java
@@ -0,0 +1,38 @@
+package com.smartdevicelink.SdlConnection;
+
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.transport.BaseTransportConfig;
+
+import java.util.List;
+
+
+public interface ISdlConnectionListener {
+ @Deprecated
+ public void onTransportDisconnected(String info);
+
+ public void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig);
+
+
+ public void onTransportError(String info, Exception e);
+
+ public void onProtocolMessageReceived(ProtocolMessage msg);
+
+ public void onProtocolSessionStartedNACKed(SessionType sessionType,
+ byte sessionID, byte version, String correlationID, List<String> rejectedParams);
+
+ public void onProtocolSessionStarted(SessionType sessionType,
+ byte sessionID, byte version, String correlationID, int hashID, boolean isEncrypted);
+
+ public void onProtocolSessionEnded(SessionType sessionType,
+ byte sessionID, String correlationID);
+
+ public void onProtocolSessionEndedNACKed(SessionType sessionType,
+ byte sessionID, String correlationID);
+
+ public void onProtocolError(String info, Exception e);
+
+ public void onHeartbeatTimedOut(byte sessionID);
+
+ public void onProtocolServiceDataACK(SessionType sessionType, int dataSize, byte sessionID);
+}
diff --git a/base/src/main/java/com/smartdevicelink/SdlConnection/SdlSession.java b/base/src/main/java/com/smartdevicelink/SdlConnection/SdlSession.java
new file mode 100644
index 000000000..1c03ea57a
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/SdlConnection/SdlSession.java
@@ -0,0 +1,748 @@
+package com.smartdevicelink.SdlConnection;
+
+import android.util.Log;
+
+import com.smartdevicelink.exception.SdlException;
+import com.smartdevicelink.protocol.ISdlProtocol;
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.protocol.SdlProtocol;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.interfaces.IAudioStreamListener;
+import com.smartdevicelink.proxy.interfaces.ISdlServiceListener;
+import com.smartdevicelink.proxy.interfaces.IVideoStreamListener;
+import com.smartdevicelink.proxy.rpc.VideoStreamingFormat;
+import com.smartdevicelink.proxy.rpc.enums.VideoStreamingProtocol;
+import com.smartdevicelink.security.ISecurityInitializedListener;
+import com.smartdevicelink.security.SdlSecurityBase;
+import com.smartdevicelink.streaming.AbstractPacketizer;
+import com.smartdevicelink.streaming.IStreamListener;
+import com.smartdevicelink.streaming.StreamPacketizer;
+import com.smartdevicelink.streaming.StreamRPCPacketizer;
+import com.smartdevicelink.streaming.video.RTPH264Packetizer;
+import com.smartdevicelink.streaming.video.VideoStreamingParameters;
+import com.smartdevicelink.transport.BaseTransportConfig;
+import com.smartdevicelink.transport.WebSocketServerConfig;
+import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.util.Version;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class SdlSession implements ISdlProtocol, ISdlConnectionListener, IStreamListener, ISecurityInitializedListener {
+
+ private static final String TAG = "SdlSession";
+
+ protected final static int BUFF_READ_SIZE = 1024;
+
+
+
+ protected BaseTransportConfig transportConfig;
+ protected ISdlConnectionListener sessionListener;
+ //FIXME protected LockScreenManager lockScreenMan = new LockScreenManager();
+ protected SdlSecurityBase sdlSecurity = null;
+ protected VideoStreamingParameters desiredVideoParams = null;
+ protected VideoStreamingParameters acceptedVideoParams = null;
+
+ protected byte sessionId;
+ protected int sessionHashId = 0;
+ protected HashMap<SessionType, CopyOnWriteArrayList<ISdlServiceListener>> serviceListeners;
+ protected CopyOnWriteArrayList<SessionType> encryptedServices = new CopyOnWriteArrayList<SessionType>();
+
+
+
+ //FIXME IHeartbeatMonitor _outgoingHeartbeatMonitor = null;
+ //FIXME IHeartbeatMonitor _incomingHeartbeatMonitor = null;
+
+ StreamRPCPacketizer mRPCPacketizer = null;
+ AbstractPacketizer mVideoPacketizer = null;
+ StreamPacketizer mAudioPacketizer = null;
+ //FIXME SdlEncoder mSdlEncoder = null;
+ //FIXME VirtualDisplayEncoder virtualDisplayEncoder = null;
+
+
+ public BaseTransportConfig getTransportConfig() {
+ return this.transportConfig;
+ }
+
+ /* FIXME public LockScreenManager getLockScreenMan() {
+ return null; //FIXME lockScreenMan;
+ } */
+
+
+ //FIXME
+ /* public IHeartbeatMonitor getOutgoingHeartbeatMonitor() {
+ return _outgoingHeartbeatMonitor;
+ }
+
+ public IHeartbeatMonitor getIncomingHeartbeatMonitor() {
+ return _incomingHeartbeatMonitor;
+ }
+
+ public void setOutgoingHeartbeatMonitor(IHeartbeatMonitor outgoingHeartbeatMonitor) {
+ this._outgoingHeartbeatMonitor = outgoingHeartbeatMonitor;
+ _outgoingHeartbeatMonitor.setListener(this);
+ }
+
+ public void setIncomingHeartbeatMonitor(IHeartbeatMonitor incomingHeartbeatMonitor) {
+ this._incomingHeartbeatMonitor = incomingHeartbeatMonitor;
+ _incomingHeartbeatMonitor.setListener(this);
+ }*/
+
+ public int getSessionHashId() {
+ return this.sessionHashId;
+ }
+
+ public byte getSessionId() {
+ return this.sessionId;
+ }
+
+
+
+ public void startStream(InputStream is, SessionType sType, byte rpcSessionID) throws IOException {
+ if (sType.equals(SessionType.NAV))
+ {
+ // protocol is fixed to RAW
+ StreamPacketizer packetizer = new StreamPacketizer(this, is, sType, rpcSessionID, this);
+ mVideoPacketizer = packetizer;
+ mVideoPacketizer.start();
+ }
+ else if (sType.equals(SessionType.PCM))
+ {
+ mAudioPacketizer = new StreamPacketizer(this, is, sType, rpcSessionID, this);
+ mAudioPacketizer.start();
+ }
+ }
+
+ public OutputStream startStream(SessionType sType, byte rpcSessionID) throws IOException {
+ OutputStream os = new PipedOutputStream();
+ InputStream is = new PipedInputStream((PipedOutputStream) os, BUFF_READ_SIZE);
+ /*InputStream is = null;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ is = new PipedInputStream((PipedOutputStream) os, BUFF_READ_SIZE);
+ } else {
+ is = new PipedInputStream((PipedOutputStream) os);
+ }*/
+ if (sType.equals(SessionType.NAV))
+ {
+ // protocol is fixed to RAW
+ StreamPacketizer packetizer = new StreamPacketizer(this, is, sType, rpcSessionID, this);
+ mVideoPacketizer = packetizer;
+ mVideoPacketizer.start();
+ }
+ else if (sType.equals(SessionType.PCM))
+ {
+ mAudioPacketizer = new StreamPacketizer(this, is, sType, rpcSessionID, this);
+ mAudioPacketizer.start();
+ }
+ else
+ {
+ os.close();
+ is.close();
+ return null;
+ }
+ return os;
+ }
+
+ public IVideoStreamListener startVideoStream() {
+ byte rpcSessionID = getSessionId();
+ VideoStreamingProtocol protocol = getAcceptedProtocol();
+ try {
+ switch (protocol) {
+ case RAW: {
+ StreamPacketizer packetizer = new StreamPacketizer(this, null, SessionType.NAV, rpcSessionID, this);
+ mVideoPacketizer = packetizer;
+ mVideoPacketizer.start();
+ return packetizer;
+ }
+ case RTP: {
+ RTPH264Packetizer packetizer = new RTPH264Packetizer(this, SessionType.NAV, rpcSessionID, this);
+ mVideoPacketizer = packetizer;
+ mVideoPacketizer.start();
+ return packetizer;
+ }
+ default:
+ Log.e(TAG, "Protocol " + protocol + " is not supported.");
+ return null;
+ }
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ public IAudioStreamListener startAudioStream() {
+ byte rpcSessionID = getSessionId();
+ try {
+ StreamPacketizer packetizer = new StreamPacketizer(this, null, SessionType.PCM, rpcSessionID, this);
+ mAudioPacketizer = packetizer;
+ mAudioPacketizer.start();
+ return packetizer;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ public void startRPCStream(InputStream is, RPCRequest request, SessionType sType, byte rpcSessionID, byte wiproVersion) {
+ try {
+ mRPCPacketizer = new StreamRPCPacketizer(null, this, is, request, sType, rpcSessionID, wiproVersion, 0, this);
+ mRPCPacketizer.start();
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to start streaming:" + e.toString());
+ }
+ }
+
+ public OutputStream startRPCStream(RPCRequest request, SessionType sType, byte rpcSessionID, byte wiproVersion) {
+ try {
+ OutputStream os = new PipedOutputStream();
+ InputStream is = new PipedInputStream((PipedOutputStream) os);
+ mRPCPacketizer = new StreamRPCPacketizer(null, this, is, request, sType, rpcSessionID, wiproVersion, 0, this);
+ mRPCPacketizer.start();
+ return os;
+ } catch (Exception e) {
+ Log.e(TAG, "Unable to start streaming:" + e.toString());
+ }
+ return null;
+ }
+
+ public void pauseRPCStream()
+ {
+ if (mRPCPacketizer != null)
+ {
+ mRPCPacketizer.pause();
+ }
+ }
+
+ public void resumeRPCStream()
+ {
+ if (mRPCPacketizer != null)
+ {
+ mRPCPacketizer.resume();
+ }
+ }
+
+ public void stopRPCStream()
+ {
+ if (mRPCPacketizer != null)
+ {
+ mRPCPacketizer.stop();
+ }
+ }
+
+ public boolean stopAudioStream()
+ {
+ if (mAudioPacketizer != null)
+ {
+ mAudioPacketizer.stop();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean stopVideoStream()
+ {
+ if (mVideoPacketizer != null)
+ {
+ mVideoPacketizer.stop();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean pauseAudioStream()
+ {
+ if (mAudioPacketizer != null)
+ {
+ mAudioPacketizer.pause();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean pauseVideoStream()
+ {
+ if (mVideoPacketizer != null)
+ {
+ mVideoPacketizer.pause();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean resumeAudioStream()
+ {
+ if (mAudioPacketizer != null)
+ {
+ mAudioPacketizer.resume();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean resumeVideoStream()
+ {
+ if (mVideoPacketizer != null)
+ {
+ mVideoPacketizer.resume();
+ return true;
+ }
+ return false;
+ }
+
+ //FIXME
+ /* public Surface createOpenGLInputSurface(int frameRate, int iFrameInterval, int width,
+ int height, int bitrate, SessionType sType, byte rpcSessionID) {
+ IVideoStreamListener encoderListener = startVideoStream();
+ if (encoderListener == null) {
+ return null;
+ }
+
+ mSdlEncoder = new SdlEncoder();
+ mSdlEncoder.setFrameRate(frameRate);
+ mSdlEncoder.setFrameInterval(iFrameInterval);
+ mSdlEncoder.setFrameWidth(width);
+ mSdlEncoder.setFrameHeight(height);
+ mSdlEncoder.setBitrate(bitrate);
+ mSdlEncoder.setOutputListener(encoderListener);
+ return mSdlEncoder.prepareEncoder();
+ }*/
+
+ public void startEncoder () {
+ /* if(mSdlEncoder != null) {
+ mSdlEncoder.startEncoder();
+ }*/
+ }
+
+ public void releaseEncoder() {
+ /* if(mSdlEncoder != null) {
+ mSdlEncoder.releaseEncoder();
+ }*/
+ }
+
+ public void drainEncoder(boolean endOfStream) {
+ /* if(mSdlEncoder != null) {
+ mSdlEncoder.drainEncoder(endOfStream);
+ }*/
+ }
+
+ @Override
+ public void sendStreamPacket(ProtocolMessage pm) {
+ sendMessage(pm);
+ }
+
+ public void setSdlSecurity(SdlSecurityBase sec) {
+ sdlSecurity = sec;
+ }
+
+ public SdlSecurityBase getSdlSecurity() {
+ return sdlSecurity;
+ }
+
+
+
+ protected void processControlService(ProtocolMessage msg) {
+ if (sdlSecurity == null)
+ return;
+ int ilen = msg.getData().length - 12;
+ byte[] data = new byte[ilen];
+ System.arraycopy(msg.getData(), 12, data, 0, ilen);
+
+ byte[] dataToRead = new byte[4096];
+
+ Integer iNumBytes = sdlSecurity.runHandshake(data, dataToRead);
+
+ if (iNumBytes == null || iNumBytes <= 0)
+ return;
+
+ byte[] returnBytes = new byte[iNumBytes];
+ System.arraycopy(dataToRead, 0, returnBytes, 0, iNumBytes);
+ ProtocolMessage protocolMessage = new ProtocolMessage();
+ protocolMessage.setSessionType(SessionType.CONTROL);
+ protocolMessage.setData(returnBytes);
+ protocolMessage.setFunctionID(0x01);
+ protocolMessage.setVersion((byte)sdlProtocol.getProtocolVersion().getMajor());
+ protocolMessage.setSessionID(getSessionId());
+
+ //sdlSecurity.hs();
+
+ sendMessage(protocolMessage);
+ }
+
+
+ protected void initialiseSession() {
+ /* FIXME if (_outgoingHeartbeatMonitor != null) {
+ _outgoingHeartbeatMonitor.start();
+ }
+ if (_incomingHeartbeatMonitor != null) {
+ _incomingHeartbeatMonitor.start();
+ }*/
+ }
+
+ public boolean isServiceProtected(SessionType sType) {
+ return encryptedServices.contains(sType);
+ }
+
+ @Override
+ public void onTransportDisconnected(String info) {
+ this.sessionListener.onTransportDisconnected(info);
+ }
+
+
+ @Override
+ public void onTransportError(String info, Exception e) {
+ this.sessionListener.onTransportError(info, e);
+ }
+
+ @Override
+ public void onProtocolMessageReceived(ProtocolMessage msg) {
+ if (msg.getSessionType().equals(SessionType.CONTROL)) {
+ processControlService(msg);
+ return;
+ }
+
+ this.sessionListener.onProtocolMessageReceived(msg);
+ }
+
+ @Override
+ public void onHeartbeatTimedOut(byte sessionID) {
+ this.sessionListener.onHeartbeatTimedOut(sessionID);
+
+ }
+
+
+ @Override
+ public void onProtocolSessionStarted(SessionType sessionType,
+ byte sessionID, byte version, String correlationID, int hashID, boolean isEncrypted) {
+
+ Log.i(TAG, "Protocol session started");
+
+ this.sessionId = sessionID;
+ //FIXME lockScreenMan.setSessionID(sessionID);
+ if (sessionType.eq(SessionType.RPC)){
+ sessionHashId = hashID;
+ }
+ if (isEncrypted)
+ encryptedServices.addIfAbsent(sessionType);
+ this.sessionListener.onProtocolSessionStarted(sessionType, sessionID, version, correlationID, hashID, isEncrypted);
+ if(serviceListeners != null && serviceListeners.containsKey(sessionType)){
+ CopyOnWriteArrayList<ISdlServiceListener> listeners = serviceListeners.get(sessionType);
+ for(ISdlServiceListener listener:listeners){
+ listener.onServiceStarted(this, sessionType, isEncrypted);
+ }
+ }
+ //if (version == 3)
+ initialiseSession();
+
+ }
+
+ @Override
+ public void onProtocolSessionEnded(SessionType sessionType, byte sessionID,
+ String correlationID) {
+ this.sessionListener.onProtocolSessionEnded(sessionType, sessionID, correlationID);
+ if(serviceListeners != null && serviceListeners.containsKey(sessionType)){
+ CopyOnWriteArrayList<ISdlServiceListener> listeners = serviceListeners.get(sessionType);
+ for(ISdlServiceListener listener:listeners){
+ listener.onServiceEnded(this, sessionType);
+ }
+ }
+ encryptedServices.remove(sessionType);
+ }
+
+ @Override
+ public void onProtocolError(String info, Exception e) {
+ this.sessionListener.onProtocolError(info, e);
+ Log.e(TAG, "on protocol error", e);
+ }
+
+
+
+
+ @Override
+ public void onProtocolSessionEndedNACKed(SessionType sessionType,
+ byte sessionID, String correlationID) {
+ this.sessionListener.onProtocolSessionEndedNACKed(sessionType, sessionID, correlationID);
+ if(serviceListeners != null && serviceListeners.containsKey(sessionType)){
+ CopyOnWriteArrayList<ISdlServiceListener> listeners = serviceListeners.get(sessionType);
+ for(ISdlServiceListener listener:listeners){
+ listener.onServiceError(this, sessionType, "End "+ sessionType.toString() +" Service NACK'ed");
+ }
+ }
+ }
+
+ @Override
+ public void onProtocolServiceDataACK(SessionType sessionType, int dataSize, byte sessionID) {
+ this.sessionListener.onProtocolServiceDataACK(sessionType, dataSize, sessionID);
+ }
+
+
+
+ public void addServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener){
+ if(serviceListeners == null){
+ serviceListeners = new HashMap<>();
+ }
+ if(serviceType != null && sdlServiceListener != null){
+ if(!serviceListeners.containsKey(serviceType)){
+ serviceListeners.put(serviceType,new CopyOnWriteArrayList<ISdlServiceListener>());
+ }
+ serviceListeners.get(serviceType).add(sdlServiceListener);
+ }
+ }
+
+ public boolean removeServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener){
+ if(serviceListeners!= null && serviceType != null && sdlServiceListener != null && serviceListeners.containsKey(serviceType)){
+ return serviceListeners.get(serviceType).remove(sdlServiceListener);
+ }
+ return false;
+ }
+
+
+ public HashMap<SessionType, CopyOnWriteArrayList<ISdlServiceListener>> getServiceListeners(){
+ return serviceListeners;
+ }
+
+ public void setDesiredVideoParams(VideoStreamingParameters params){
+ this.desiredVideoParams = params;
+ }
+
+ /**
+ * Returns the currently set desired video streaming parameters. If there haven't been any set,
+ * the default options will be returned and set for this instance.
+ * @return
+ */
+ public VideoStreamingParameters getDesiredVideoParams(){
+ if(desiredVideoParams == null){
+ desiredVideoParams = new VideoStreamingParameters();
+ }
+ return desiredVideoParams;
+ }
+
+ public void setAcceptedVideoParams(VideoStreamingParameters params){
+ this.acceptedVideoParams = params;
+ }
+
+ public VideoStreamingParameters getAcceptedVideoParams(){
+ return acceptedVideoParams;
+ }
+
+ private VideoStreamingProtocol getAcceptedProtocol() {
+ // acquire default protocol (RAW)
+ VideoStreamingProtocol protocol = new VideoStreamingParameters().getFormat().getProtocol();
+
+ if (acceptedVideoParams != null) {
+ VideoStreamingFormat format = acceptedVideoParams.getFormat();
+ if (format != null && format.getProtocol() != null) {
+ protocol = format.getProtocol();
+ }
+ }
+
+ return protocol;
+ }
+
+
+/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~`~
+ * SdlSession 2
+ *
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+*/
+ final protected SdlProtocol sdlProtocol;
+
+ public SdlSession(ISdlConnectionListener listener, WebSocketServerConfig config){
+ //FIXME public SdlSession2(ISdlConnectionListener listener, MultiplexTransportConfig config){
+ this.transportConfig = config;
+ this.sessionListener = listener;
+ this.sdlProtocol = new SdlProtocol(this,config);
+
+ }
+
+
+ public int getMtu(){
+ if(this.sdlProtocol!=null){
+ return this.sdlProtocol.getMtu();
+ }else{
+ return 0;
+ }
+ }
+
+ public long getMtu(SessionType type) {
+ if (this.sdlProtocol != null) {
+ return this.sdlProtocol.getMtu(type);
+ } else {
+ return 0;
+ }
+ }
+
+ public void close() {
+ if (sdlSecurity != null)
+ {
+ sdlSecurity.resetParams();
+ sdlSecurity.shutDown();
+ }
+ if(sdlProtocol != null){
+ sdlProtocol.endSession(sessionId, sessionHashId);
+ }
+ }
+
+
+ public void startService (SessionType serviceType, byte sessionID, boolean isEncrypted) {
+ if (isEncrypted){
+ if (sdlSecurity != null){
+ List<SessionType> serviceList = sdlSecurity.getServiceList();
+ if (!serviceList.contains(serviceType))
+ serviceList.add(serviceType);
+
+ sdlSecurity.initialize();
+ }
+ return;
+ }
+ sdlProtocol.startService(serviceType, sessionID, isEncrypted);
+ }
+
+ public void endService (SessionType serviceType, byte sessionID) {
+ if (sdlProtocol == null) {
+ return;
+ }
+ sdlProtocol.endService(serviceType,sessionID);
+ }
+
+
+ public void startSession() throws SdlException {
+ sdlProtocol.start();
+ }
+
+
+ public void sendMessage(ProtocolMessage msg) {
+ if (sdlProtocol == null){
+ return;
+ }
+ sdlProtocol.sendMessage(msg);
+ }
+
+ public TransportType getCurrentTransportType() {
+ return TransportType.WEB_SOCKET_SERVER;
+ }
+
+ public boolean getIsConnected() {
+ return sdlProtocol != null && sdlProtocol.isConnected();
+ }
+
+
+ public void shutdown(String info){
+ Log.d(TAG, "Shutdown - " + info);
+ this.sessionListener.onTransportDisconnected(info);
+
+ }
+
+ @Override
+ public void onTransportDisconnected(String info, boolean altTransportAvailable, BaseTransportConfig transportConfig) {
+ this.sessionListener.onTransportDisconnected(info, altTransportAvailable, this.transportConfig);
+ }
+
+ /**
+ * Get the current protocol version used by this session
+ * @return Version that represents the Protocol version being used
+ */
+ public Version getProtocolVersion(){
+ if(sdlProtocol!=null){
+ return sdlProtocol.getProtocolVersion();
+ }
+ return new Version(1,0,0);
+ }
+
+
+ /* ***********************************************************************************************************************************************************************
+ * ***************************************************************** IProtocol Listener ********************************************************************************
+ *************************************************************************************************************************************************************************/
+
+ public void onProtocolMessageBytesToSend(SdlPacket packet) {
+ //Log.d(TAG, "onProtocolMessageBytesToSend - " + packet.getTransportType());
+ sdlProtocol.sendPacket(packet);
+ }
+
+
+ public void onProtocolSessionStartedNACKed(SessionType sessionType, byte sessionID, byte version, String correlationID, List<String> rejectedParams){
+ onProtocolSessionNACKed(sessionType,sessionID,version,correlationID,rejectedParams);
+ }
+
+ public void onProtocolSessionNACKed(SessionType sessionType, byte sessionID, byte version, String correlationID, List<String> rejectedParams) {
+ this.sessionListener.onProtocolSessionStartedNACKed(sessionType,
+ sessionID, version, correlationID, rejectedParams);
+ if(serviceListeners != null && serviceListeners.containsKey(sessionType)){
+ CopyOnWriteArrayList<ISdlServiceListener> listeners = serviceListeners.get(sessionType);
+ for(ISdlServiceListener listener:listeners){
+ listener.onServiceError(this, sessionType, "Start "+ sessionType.toString() +" Service NAKed");
+ }
+ }
+ }
+
+ /* Not supported methods from IProtocolListener */
+ public void onProtocolHeartbeat(SessionType sessionType, byte sessionID) { /* Not supported */}
+ public void onProtocolHeartbeatACK(SessionType sessionType, byte sessionID) {/* Not supported */}
+ public void onResetOutgoingHeartbeat(SessionType sessionType, byte sessionID) {/* Not supported */}
+ public void onResetIncomingHeartbeat(SessionType sessionType, byte sessionID) {/* Not supported */}
+
+ /* ***********************************************************************************************************************************************************************
+ * ***************************************************************** Security Listener *********************************************************************************
+ *************************************************************************************************************************************************************************/
+
+
+ @Override
+ public void onSecurityInitialized() {
+
+ if (sdlProtocol != null && sdlSecurity != null)
+ {
+ List<SessionType> list = sdlSecurity.getServiceList();
+
+ SessionType service;
+ ListIterator<SessionType> iter = list.listIterator();
+
+ while (iter.hasNext()) {
+ service = iter.next();
+
+ if (service != null)
+ sdlProtocol.startService(service, getSessionId(), true);
+
+ iter.remove();
+ }
+ }
+ }
+
+ @Override
+ public void stopStream(SessionType serviceType) {
+ if(SessionType.NAV.equals(serviceType)){
+ stopVideoStream();
+ }else if(SessionType.PCM.equals(serviceType)){
+ stopAudioStream();
+ }
+
+ }
+
+ /**
+ * Check to see if a transport is available to start/use the supplied service.
+ * @param sessionType the session that should be checked for transport availability
+ * @return true if there is either a supported
+ * transport currently connected or a transport is
+ * available to connect with for the supplied service type.
+ * <br>false if there is no
+ * transport connected to support the service type in question and
+ * no possibility in the foreseeable future.
+ */
+ public boolean isTransportForServiceAvailable(SessionType sessionType){
+ return sdlProtocol!=null && sdlProtocol.isTransportForServiceAvailable(sessionType);
+ }
+
+
+ @Deprecated
+ public void clearConnection(){/* Not supported */}
+
+
+
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/exception/SdlException.java b/base/src/main/java/com/smartdevicelink/exception/SdlException.java
new file mode 100644
index 000000000..852406155
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/exception/SdlException.java
@@ -0,0 +1,47 @@
+package com.smartdevicelink.exception;
+
+
+public class SdlException extends Exception {
+
+ private static final long serialVersionUID = 5922492291870772815L;
+
+ protected Throwable detail = null;
+ private SdlExceptionCause _sdlExceptionCause = null;
+
+ public SdlException(String msg, SdlExceptionCause exceptionCause) {
+ super(msg);
+ _sdlExceptionCause = exceptionCause;
+ }
+
+ public SdlException(String msg, Throwable ex, SdlExceptionCause exceptionCause) {
+ super(msg + " --- Check inner exception for diagnostic details");
+ detail = ex;
+ _sdlExceptionCause = exceptionCause;
+ }
+
+ public SdlException(Throwable ex) {
+ super(ex.getMessage());
+ detail = ex;
+ }
+
+ public SdlExceptionCause getSdlExceptionCause() {
+ return _sdlExceptionCause;
+ }
+
+ public Throwable getInnerException() {
+ return detail;
+ }
+
+ public String toString() {
+ String ret = this.getClass().getName();
+ ret += ": " + this.getMessage();
+ if(this.getSdlExceptionCause() != null){
+ ret += "\nSdlExceptionCause: " + this.getSdlExceptionCause().name();
+ }
+ if (detail != null) {
+ ret += "\nnested: " + detail.toString();
+ detail.printStackTrace();
+ }
+ return ret;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/exception/SdlExceptionCause.java b/base/src/main/java/com/smartdevicelink/exception/SdlExceptionCause.java
new file mode 100644
index 000000000..ee5ea8c9f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/exception/SdlExceptionCause.java
@@ -0,0 +1,25 @@
+package com.smartdevicelink.exception;
+
+public enum SdlExceptionCause {
+ BLUETOOTH_ADAPTER_NULL,
+ BLUETOOTH_DISABLED,
+ BLUETOOTH_SOCKET_UNAVAILABLE,
+ HEARTBEAT_PAST_DUE,
+ INCORRECT_LIFECYCLE_MODEL,
+ INVALID_ARGUMENT,
+ INVALID_RPC_PARAMETER,
+ PERMISSION_DENIED,
+ RESERVED_CORRELATION_ID,
+ SDL_CONNECTION_FAILED,
+ SDL_PROXY_CYCLED,
+ SDL_PROXY_DISPOSED,
+ SDL_PROXY_OBSOLETE,
+ SDL_REGISTRATION_ERROR,
+ SDL_UNAVAILABLE,
+ INVALID_HEADER,
+ DATA_BUFFER_NULL,
+ SDL_USB_DETACHED,
+ SDL_USB_PERMISSION_DENIED,
+ LOCK_SCREEN_ICON_NOT_SUPPORTED,
+ ;
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java
new file mode 100644
index 000000000..35b202e0a
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java
@@ -0,0 +1,4 @@
+package com.smartdevicelink.managers;
+
+abstract class BaseSdlManager {
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManagerListener.java b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManagerListener.java
new file mode 100644
index 000000000..76d5d50e2
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManagerListener.java
@@ -0,0 +1,4 @@
+package com.smartdevicelink.managers;
+
+public interface BaseSdlManagerListener {
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/BaseSubManager.java b/base/src/main/java/com/smartdevicelink/managers/BaseSubManager.java
new file mode 100644
index 000000000..6475b481e
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/BaseSubManager.java
@@ -0,0 +1,83 @@
+package com.smartdevicelink.managers;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.proxy.interfaces.ISdl;
+import com.smartdevicelink.transport.utl.TransportRecord;
+
+import java.util.List;
+
+/**
+ * <strong>BaseSubManager</strong> <br>
+ *
+ * Note: This class is extended by SubManagers <br>
+ *
+ * It is broken down to these areas: <br>
+ *
+ * 1. <br>
+ */
+public abstract class BaseSubManager {
+
+ // states - if this gets more complicated we can move elsewhere
+ private int state;
+ private final Object STATE_LOCK = new Object();
+ public static final int SETTING_UP = 0x00, READY = 0x30, LIMITED = 0x50, SHUTDOWN = 0x80, ERROR = 0xC0;
+ protected final ISdl internalInterface;
+ private CompletionListener completionListener;
+
+ public BaseSubManager(@NonNull ISdl internalInterface){
+ this.internalInterface = internalInterface;
+ transitionToState(SETTING_UP);
+ }
+
+ /**
+ * Starts up a BaseSubManager, and calls provided callback once BaseSubManager is done setting up or failed setup.
+ * @param listener CompletionListener that is called once the BaseSubManager's state is READY, LIMITED, or ERROR
+ */
+ public void start(CompletionListener listener){
+ this.completionListener = listener;
+ int state = getState();
+ if((state == READY || state == LIMITED || state == ERROR) && completionListener != null){
+ completionListener.onComplete(state == READY || state == LIMITED);
+ completionListener = null;
+ }
+ }
+
+ /**
+ * <p>Called when manager is being torn down</p>
+ */
+ public void dispose(){
+ transitionToState(SHUTDOWN);
+ }
+
+ protected void transitionToState(int state) {
+ synchronized (STATE_LOCK) {
+ this.state = state;
+ }
+ if((state == READY || state == LIMITED || state == ERROR) && completionListener != null ){
+ completionListener.onComplete(state == READY || state == LIMITED);
+ completionListener = null;
+ }
+ }
+
+ public int getState() {
+ synchronized (STATE_LOCK) {
+ return state;
+ }
+ }
+
+ //This allows the method to not be exposed to developers
+ protected void handleTransportUpdated(List<TransportRecord> connectedTransports, boolean audioStreamTransportAvail, boolean videoStreamTransportAvail){
+ this.onTransportUpdate(connectedTransports,audioStreamTransportAvail,videoStreamTransportAvail);
+ }
+
+ /**
+ * Transport status has been updated
+ * @param connectedTransports currently connected transports
+ * @param audioStreamTransportAvail if there is a transport that could be used to carry the
+ * audio service
+ * @param videoStreamTransportAvail if there is a transport that could be used to carry the
+ * video service
+ */
+ protected void onTransportUpdate(List<TransportRecord> connectedTransports, boolean audioStreamTransportAvail, boolean videoStreamTransportAvail){}
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/CompletionListener.java b/base/src/main/java/com/smartdevicelink/managers/CompletionListener.java
new file mode 100644
index 000000000..93e618dae
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/CompletionListener.java
@@ -0,0 +1,10 @@
+package com.smartdevicelink.managers;
+
+public interface CompletionListener {
+
+ /**
+ * Returns whether a specific operation was successful or not
+ * @param success - success or fail
+ */
+ void onComplete(boolean success);
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/StreamingStateMachine.java b/base/src/main/java/com/smartdevicelink/managers/StreamingStateMachine.java
new file mode 100644
index 000000000..caabf67a2
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/StreamingStateMachine.java
@@ -0,0 +1,72 @@
+package com.smartdevicelink.managers;
+
+import android.support.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class StreamingStateMachine {
+ @IntDef({NONE, READY, STARTED, STOPPED, ERROR})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StreamingState {}
+ public static final int NONE = 0x00, READY = 0x30, STARTED = 0x60, STOPPED = 0x90, ERROR = 0xC0;
+
+ private @StreamingState int state = NONE;
+ private final Object STATE_LOCK = new Object();
+
+ public StreamingStateMachine(){}
+
+ public void transitionToState(int state) {
+ if(state != NONE && state != READY && state != STARTED
+ && state != STOPPED && state != ERROR) {
+ return;
+ }
+ synchronized (STATE_LOCK) {
+ if(isValidTransition(this.state, state)){
+ this.state = state;
+ }
+ }
+ }
+
+ public @StreamingState int getState() {
+ synchronized (STATE_LOCK) {
+ return state;
+ }
+ }
+
+ private boolean isValidTransition(int prev_state, int next_state){
+ if(prev_state == next_state){
+ return false;
+ }
+ switch (prev_state){
+ case NONE:
+ if((next_state == READY) || (next_state == ERROR)){
+ return true;
+ }
+ break;
+ case READY:
+ if((next_state == STARTED) || (next_state == ERROR)){
+ return true;
+ }
+ break;
+ case STARTED:
+ if((next_state == STOPPED) || (next_state == ERROR)){
+ return true;
+ }
+ break;
+ case STOPPED:
+ if((next_state == STARTED) || (next_state == NONE)){
+ return true;
+ }
+ break;
+ case ERROR:
+ if(next_state == NONE){
+ return true;
+ }
+ break;
+ default:
+ break;
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/audio/BaseAudioStreamManager.java b/base/src/main/java/com/smartdevicelink/managers/audio/BaseAudioStreamManager.java
new file mode 100644
index 000000000..4be991674
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/audio/BaseAudioStreamManager.java
@@ -0,0 +1,11 @@
+package com.smartdevicelink.managers.audio;
+
+import android.support.annotation.NonNull;
+import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+
+abstract class BaseAudioStreamManager extends BaseSubManager {
+ public BaseAudioStreamManager(@NonNull ISdl internalInterface) {
+ super(internalInterface);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
new file mode 100644
index 000000000..2fbd4cea9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/BaseFileManager.java
@@ -0,0 +1,339 @@
+package com.smartdevicelink.managers.file;
+
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.managers.file.filetypes.SdlFile;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+import com.smartdevicelink.proxy.rpc.DeleteFile;
+import com.smartdevicelink.proxy.rpc.ListFiles;
+import com.smartdevicelink.proxy.rpc.ListFilesResponse;
+import com.smartdevicelink.proxy.rpc.PutFile;
+import com.smartdevicelink.proxy.rpc.enums.Result;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <strong>FileManager</strong> <br>
+ *
+ * Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br>
+ *
+ * The SDLFileManager uploads files and keeps track of all the uploaded files names during a session. <br>
+ *
+ * We need to add the following struct: SDLFile<br>
+ *
+ * It is broken down to these areas: <br>
+ *
+ * 1. Getters <br>
+ * 2. Deletion methods <br>
+ * 3. Uploading Files / Artwork
+ */
+abstract class BaseFileManager extends BaseSubManager {
+
+ final static String TAG = "FileManager";
+ private List<String> remoteFiles, uploadedEphemeralFileNames;
+
+ public BaseFileManager(ISdl internalInterface) {
+
+ // setup
+ super(internalInterface);
+ uploadedEphemeralFileNames = new ArrayList<>();
+ }
+
+ @Override
+ public void start(CompletionListener listener) {
+ // prepare manager - don't set state to ready until we have list of files
+ retrieveRemoteFiles();
+ super.start(listener);
+ }
+
+ // GETTERS
+
+ /**
+ * Returns a list of file names currently residing on core
+ * @return List<String> of remote file names
+ */
+ public List<String> getRemoteFileNames() {
+ if (getState() != BaseSubManager.READY){
+ // error and dont return list
+ throw new IllegalArgumentException("FileManager is not READY");
+ }
+ // return list (this is synchronous at this point)
+ return remoteFiles;
+ }
+
+ private void retrieveRemoteFiles(){
+ remoteFiles = new ArrayList<>();
+ // hold list in remoteFiles class var
+ ListFiles listFiles = new ListFiles();
+ listFiles.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if(response.getSuccess()){
+ if(((ListFilesResponse) response).getFilenames() != null){
+ remoteFiles.addAll(((ListFilesResponse) response).getFilenames());
+ }
+ // on callback set manager to ready state
+ transitionToState(BaseSubManager.READY);
+ }
+ }
+
+ @Override
+ public void onError(int correlationId, Result resultCode, String info) {
+ // file list could not be received
+ transitionToState(BaseSubManager.ERROR);
+ }
+ });
+ internalInterface.sendRPCRequest(listFiles);
+ }
+
+ // DELETION
+
+ /**
+ * Attempts to delete the desired file from core, calls listener with indication of success/failure
+ * @param fileName name of file to be deleted
+ * @param listener callback that is called on response from core
+ */
+ public void deleteRemoteFileWithName(@NonNull final String fileName, final CompletionListener listener){
+ DeleteFile deleteFile = new DeleteFile();
+ deleteFile.setSdlFileName(fileName);
+ deleteFile.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if(response.getSuccess()){
+ remoteFiles.remove(fileName);
+ uploadedEphemeralFileNames.remove(fileName);
+ }
+ if(listener != null){
+ listener.onComplete(response.getSuccess());
+ }
+ }
+ });
+ internalInterface.sendRPCRequest(deleteFile);
+ }
+
+ /**
+ * Attempts to delete a list of files from core, calls listener with indication of success/failure
+ * @param fileNames list of file names to be deleted
+ * @param listener callback that is called once core responds to all deletion requests
+ */
+ public void deleteRemoteFilesWithNames(@NonNull List<String> fileNames, final MultipleFileCompletionListener listener){
+ if(fileNames.isEmpty()){
+ return;
+ }
+ final List<DeleteFile> deleteFileRequests = new ArrayList<>();
+ for(String fileName : fileNames){
+ DeleteFile deleteFile = new DeleteFile();
+ deleteFile.setSdlFileName(fileName);
+ deleteFileRequests.add(deleteFile);
+ }
+ sendMultipleFileOperations(deleteFileRequests, listener);
+ }
+
+ // UPLOAD FILES / ARTWORK
+
+ /**
+ * Creates and returns a PutFile request that would upload a given SdlFile
+ * @param file SdlFile with fileName and one of A) fileData, B) Uri, or C) resourceID set
+ * @return a valid PutFile request if SdlFile contained a fileName and sufficient data
+ */
+ abstract PutFile createPutFile(@NonNull final SdlFile file);
+
+ /**
+ * Sends list of provided requests (strictly PutFile or DeleteFile) asynchronously through internalInterface,
+ * calls listener on conclusion of sending requests.
+ * @param requests Non-empty list of PutFile or DeleteFile requests
+ * @param listener MultipleFileCompletionListener that is called upon conclusion of sending requests
+ */
+ private void sendMultipleFileOperations(final List<? extends RPCRequest> requests, final MultipleFileCompletionListener listener){
+ final Map<String, String> errors = new HashMap<>();
+ final HashMap<Integer,String> fileNameMap = new HashMap<>(); //FIXME changed from sparse array
+ final boolean deletionOperation;
+ if(requests.get(0) instanceof PutFile){
+ deletionOperation = false;
+ }else if(requests.get(0) instanceof DeleteFile){
+ deletionOperation = true;
+ }else{
+ return; // requests are not DeleteFile or PutFile
+ }
+ internalInterface.sendRequests(requests, new OnMultipleRequestListener() {
+ int fileNum = 0;
+
+ @Override
+ public void addCorrelationId(int correlationid) {
+ super.addCorrelationId(correlationid);
+ if(deletionOperation){
+ fileNameMap.put(correlationid, ((DeleteFile) requests.get(fileNum++)).getSdlFileName());
+ }else{
+ fileNameMap.put(correlationid, ((PutFile) requests.get(fileNum++)).getSdlFileName());
+ }
+ }
+
+ @Override
+ public void onUpdate(int remainingRequests) {}
+
+ @Override
+ public void onFinished() {
+ if(listener != null) {
+ if (errors.isEmpty()) {
+ listener.onComplete(null);
+ } else {
+ listener.onComplete(errors);
+ }
+ }
+ }
+
+ @Override
+ public void onError(int correlationId, Result resultCode, String info) {
+ if(fileNameMap.get(correlationId) != null){
+ errors.put(fileNameMap.get(correlationId), buildErrorString(resultCode, info));
+ }// else no fileName for given correlation ID
+ }
+
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if(response.getSuccess()){
+ if(fileNameMap.get(correlationId) != null){
+ if(deletionOperation){
+ remoteFiles.remove(fileNameMap.get(correlationId));
+ uploadedEphemeralFileNames.remove(fileNameMap.get(correlationId));
+ }else{
+ remoteFiles.add(fileNameMap.get(correlationId));
+ uploadedEphemeralFileNames.add(fileNameMap.get(correlationId));
+ }
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Attempts to upload a SdlFile to core
+ * @param file SdlFile with file name and one of A) fileData, B) Uri, or C) resourceID set
+ * @param listener called when core responds to the attempt to upload the file
+ */
+ public void uploadFile(@NonNull final SdlFile file, final CompletionListener listener){
+ PutFile putFile = createPutFile(file);
+
+ putFile.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if(response.getSuccess()){
+ remoteFiles.add(file.getName());
+ uploadedEphemeralFileNames.add(file.getName());
+ }
+ if(listener != null){
+ listener.onComplete(response.getSuccess());
+ }
+ }
+
+ @Override
+ public void onError(int correlationId, Result resultCode, String info) {
+ super.onError(correlationId, resultCode, info);
+ if(listener != null){
+ listener.onComplete(false);
+ }
+ }
+ });
+
+ internalInterface.sendRPCRequest(putFile);
+ }
+
+ /**
+ * Attempts to upload a list of SdlFiles to core
+ * @param files list of SdlFiles with file name and one of A) fileData, B) Uri, or C) resourceID set
+ * @param listener callback that is called once core responds to all upload requests
+ */
+ public void uploadFiles(@NonNull List<? extends SdlFile> files, final MultipleFileCompletionListener listener){
+ if(files.isEmpty()){
+ return;
+ }
+ final List<PutFile> putFileRequests = new ArrayList<>();
+ for(SdlFile file : files){
+ putFileRequests.add(createPutFile(file));
+ }
+ sendMultipleFileOperations(putFileRequests, listener);
+ }
+
+ /**
+ * Attempts to upload a SdlArtwork to core
+ * @param file SdlArtwork with file name and one of A) fileData, B) Uri, or C) resourceID set
+ * @param listener called when core responds to the attempt to upload the file
+ */
+ public void uploadArtwork(final SdlArtwork file, final CompletionListener listener){
+ uploadFile(file, listener);
+ }
+
+ /**
+ * Attempts to upload a list of SdlArtworks to core
+ * @param files list of SdlArtworks with file name and one of A) fileData, B) Uri, or C) resourceID set
+ * @param listener callback that is called once core responds to all upload requests
+ */
+ public void uploadArtworks(List<SdlArtwork> files, final MultipleFileCompletionListener listener){
+ uploadFiles(files, listener);
+ }
+
+ /**
+ * Check if an SdlFile has been uploaded to core
+ * @param file SdlFile
+ * @return boolean that tells whether file has been uploaded to core (true) or not (false)
+ */
+ public boolean hasUploadedFile(@NonNull SdlFile file){
+ if(file.isPersistent() && remoteFiles != null && remoteFiles.contains(file.getName())){
+ return true;
+ }else if(!file.isPersistent() && remoteFiles != null && remoteFiles.contains(file.getName())
+ && uploadedEphemeralFileNames.contains(file.getName())){
+ return true;
+ }
+ return false;
+ }
+
+ // HELPERS
+
+ /**
+ * Builds an error string for a given Result and info string
+ * @param resultCode Result
+ * @param info String returned from OnRPCRequestListener.onError()
+ * @return Error string
+ */
+ static public String buildErrorString(Result resultCode, String info){
+ return resultCode.toString() + " : " + info;
+ }
+
+ /**
+ * Helper method to take InputStream and turn it into byte array
+ * @param is valid InputStream
+ * @return Resulting byte array
+ */
+ byte[] contentsOfInputStream(InputStream is){
+ if(is == null){
+ return null;
+ }
+ try{
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ final int bufferSize = 4096;
+ final byte[] buffer = new byte[bufferSize];
+ int available;
+ while ((available = is.read(buffer)) >= 0) {
+ os.write(buffer, 0, available);
+ }
+ return os.toByteArray();
+ } catch (IOException e){
+ Log.e(TAG, "Can't read from InputStream", e);
+ return null;
+ }
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/file/MultipleFileCompletionListener.java b/base/src/main/java/com/smartdevicelink/managers/file/MultipleFileCompletionListener.java
new file mode 100644
index 000000000..803c54f64
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/file/MultipleFileCompletionListener.java
@@ -0,0 +1,14 @@
+package com.smartdevicelink.managers.file;
+
+import java.util.Map;
+
+public interface MultipleFileCompletionListener {
+
+ /**
+ * @param errors - a dictionary (map) property, of type <String: String>, and contains information
+ * on all failed uploads. The key is the name of the file that did not upload properly,
+ * the value is an error String describing what went wrong on that particular upload attempt.
+ * If all files are uploaded successfully, errors is null
+ */
+ void onComplete(Map<String, String> errors);
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
new file mode 100644
index 000000000..67d00a1e4
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java
@@ -0,0 +1,6 @@
+package com.smartdevicelink.managers.lifecycle;
+
+import com.smartdevicelink.SdlConnection.ISdlConnectionListener;
+
+abstract class BaseLifecycleManager implements ISdlConnectionListener {
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java
new file mode 100644
index 000000000..c301da8de
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java
@@ -0,0 +1,237 @@
+package com.smartdevicelink.managers.lifecycle;
+
+import android.util.Log;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.rpc.Headers;
+import com.smartdevicelink.proxy.rpc.OnSystemRequest;
+import com.smartdevicelink.proxy.rpc.PutFile;
+import com.smartdevicelink.proxy.rpc.SystemRequest;
+import com.smartdevicelink.proxy.rpc.enums.FileType;
+import com.smartdevicelink.proxy.rpc.enums.RequestType;
+import com.smartdevicelink.util.DebugTool;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.*;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.util.List;
+import java.util.Vector;
+
+// FIXME find a better name
+class PoliciesFetcher {
+
+ private static final String TAG = PoliciesFetcher.class.getSimpleName();
+ private static final int POLICIES_CORRELATION_ID = 65535;
+
+ private static HttpURLConnection getURLConnection(Headers myHeader, String sURLString, int Timeout, int iContentLen) {
+ String sContentType = "application/json";
+ int CONNECTION_TIMEOUT = Timeout * 1000;
+ int READ_TIMEOUT = Timeout * 1000;
+ boolean bDoOutput = true;
+ boolean bDoInput = true;
+ boolean bUsesCaches = false;
+ String sRequestMeth = "POST";
+
+ boolean bInstFolRed = false;
+ String sCharSet = "utf-8";
+ int iContentLength = iContentLen;
+
+ URL url;
+ HttpURLConnection urlConnection;
+
+ if (myHeader != null) {
+ //if the header isn't null, use it and replace the hardcoded params
+ int iTimeout;
+ int iReadTimeout;
+ sContentType = myHeader.getContentType();
+ iTimeout = myHeader.getConnectTimeout();
+ bDoOutput = myHeader.getDoOutput();
+ bDoInput = myHeader.getDoInput();
+ bUsesCaches = myHeader.getUseCaches();
+ sRequestMeth = myHeader.getRequestMethod();
+ iReadTimeout = myHeader.getReadTimeout();
+ bInstFolRed = myHeader.getInstanceFollowRedirects();
+ sCharSet = myHeader.getCharset();
+ iContentLength = myHeader.getContentLength();
+ CONNECTION_TIMEOUT = iTimeout * 1000;
+ READ_TIMEOUT = iReadTimeout * 1000;
+ }
+
+ try {
+ url = new URL(sURLString);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setConnectTimeout(CONNECTION_TIMEOUT);
+ urlConnection.setDoOutput(bDoOutput);
+ urlConnection.setDoInput(bDoInput);
+ urlConnection.setRequestMethod(sRequestMeth);
+ urlConnection.setReadTimeout(READ_TIMEOUT);
+ urlConnection.setInstanceFollowRedirects(bInstFolRed);
+ urlConnection.setRequestProperty("Content-Type", sContentType);
+ urlConnection.setRequestProperty("charset", sCharSet);
+ urlConnection.setRequestProperty("Content-Length", "" + Integer.toString(iContentLength));
+ urlConnection.setUseCaches(bUsesCaches);
+ return urlConnection;
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+
+ public static RPCRequest fetchPolicies(OnSystemRequest msg) {
+ HttpURLConnection urlConnection = null;
+ boolean bLegacy = false;
+
+ String sURLString = msg.getUrl();
+
+ Integer iTimeout = msg.getTimeout();
+
+ if (iTimeout == null)
+ iTimeout = 2000;
+
+ Headers myHeader = msg.getHeader();
+
+ try {
+ String sBodyString = msg.getBody();
+
+ JSONObject jsonObjectToSendToServer;
+ String valid_json = "";
+ int length;
+ if (sBodyString == null) {
+ if (RequestType.HTTP.equals(msg.getRequestType())) {
+ length = msg.getBulkData().length;
+ } else {
+ List<String> legacyData = msg.getLegacyData();
+ JSONArray jsonArrayOfSdlPPackets = new JSONArray(legacyData);
+ jsonObjectToSendToServer = new JSONObject();
+ jsonObjectToSendToServer.put("data", jsonArrayOfSdlPPackets);
+ bLegacy = true;
+ valid_json = jsonObjectToSendToServer.toString().replace("\\", "");
+ length = valid_json.getBytes("UTF-8").length;
+ }
+ } else {
+ valid_json = sBodyString.replace("\\", "");
+ length = valid_json.getBytes("UTF-8").length;
+ }
+
+ urlConnection = getURLConnection(myHeader, sURLString, iTimeout, length);
+
+ if (urlConnection == null) {
+ Log.i(TAG, "urlConnection is null, check RPC input parameters");
+ return null;
+ }
+
+ DataOutputStream wr = new DataOutputStream(urlConnection.getOutputStream());
+ if (RequestType.HTTP.equals(msg.getRequestType())) {
+ wr.write(msg.getBulkData());
+ } else {
+ wr.writeBytes(valid_json);
+ }
+
+ wr.flush();
+ wr.close();
+
+
+ long BeforeTime = System.currentTimeMillis();
+ long AfterTime = System.currentTimeMillis();
+ final long roundtriptime = AfterTime - BeforeTime;
+
+ int iResponseCode = urlConnection.getResponseCode();
+
+ if (iResponseCode != HttpURLConnection.HTTP_OK) {
+ Log.i(TAG, "Response code not HTTP_OK, returning from sendOnSystemRequestToUrl.");
+ return null;
+ }
+
+ InputStream is = urlConnection.getInputStream();
+ BufferedReader rd = new BufferedReader(new InputStreamReader(is));
+ String line;
+ StringBuilder response = new StringBuilder();
+ while ((line = rd.readLine()) != null) {
+ response.append(line);
+ response.append('\r');
+ }
+ rd.close();
+ //We've read the body
+ if (RequestType.HTTP.equals(msg.getRequestType())) {
+ // Create the SystemRequest RPC to send to module.
+ PutFile putFile = new PutFile();
+ putFile.setFileType(FileType.JSON);
+ putFile.setCorrelationID(POLICIES_CORRELATION_ID);
+ putFile.setSdlFileName("response_data");
+ putFile.setFileData(response.toString().getBytes("UTF-8"));
+ putFile.setCRC(response.toString().getBytes());
+ return putFile;
+ } else {
+ Vector<String> cloudDataReceived = new Vector<String>();
+ final String dataKey = "data";
+ // Convert the response to JSON
+ JSONObject jsonResponse = new JSONObject(response.toString());
+ if (jsonResponse.has(dataKey)) {
+ if (jsonResponse.get(dataKey) instanceof JSONArray) {
+ JSONArray jsonArray = jsonResponse.getJSONArray(dataKey);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ if (jsonArray.get(i) instanceof String) {
+ cloudDataReceived.add(jsonArray.getString(i));
+ //Log.i("sendSystemRequestToUrl", "jsonArray.getString(i): " + jsonArray.getString(i));
+ }
+ }
+ } else if (jsonResponse.get(dataKey) instanceof String) {
+ cloudDataReceived.add(jsonResponse.getString(dataKey));
+ //Log.i("sendSystemRequestToUrl", "jsonResponse.getString(data): " + jsonResponse.getString("data"));
+ }
+ } else {
+ DebugTool.logError("sendSystemRequestToUrl: Data in JSON Object neither an array nor a string.");
+ //Log.i("sendSystemRequestToUrl", "sendSystemRequestToUrl: Data in JSON Object neither an array nor a string.");
+ return null;
+ }
+
+ String sResponse = cloudDataReceived.toString();
+
+ if (sResponse.length() > 512) {
+ sResponse = sResponse.substring(0, 511);
+ }
+
+ // Send new SystemRequest to SDL
+ SystemRequest mySystemRequest = null;
+
+ if (bLegacy) {
+ if (cloudDataReceived != null) {
+ mySystemRequest = new SystemRequest(true);
+ mySystemRequest.setCorrelationID(POLICIES_CORRELATION_ID);
+ mySystemRequest.setLegacyData(cloudDataReceived);
+ }
+ } else {
+ if (response != null) {
+ mySystemRequest = new SystemRequest();
+ mySystemRequest.setRequestType(RequestType.PROPRIETARY);
+ mySystemRequest.setCorrelationID(POLICIES_CORRELATION_ID);
+ mySystemRequest.setBulkData(response.toString().getBytes());
+ }
+ }
+ return mySystemRequest;
+
+ }
+ } catch (JSONException e) {
+ DebugTool.logError("sendSystemRequestToUrl: JSONException: ", e);
+ } catch (UnsupportedEncodingException e) {
+ DebugTool.logError("sendSystemRequestToUrl: Could not encode string.", e);
+ } catch (ProtocolException e) {
+ DebugTool.logError("sendSystemRequestToUrl: Could not set request method to post.", e);
+ } catch (MalformedURLException e) {
+ DebugTool.logError("sendSystemRequestToUrl: URL Exception when sending SystemRequest to an external server.", e);
+ } catch (IOException e) {
+ DebugTool.logError("sendSystemRequestToUrl: IOException: ", e);
+ } catch (Exception e) {
+ DebugTool.logError("sendSystemRequestToUrl: Unexpected Exception: ", e);
+ } finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ }
+ return null;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java
new file mode 100644
index 000000000..020677924
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java
@@ -0,0 +1,146 @@
+package com.smartdevicelink.managers.lifecycle;
+
+import android.util.Log;
+import com.smartdevicelink.marshal.JsonRPCMarshaller;
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCMessage;
+import com.smartdevicelink.proxy.RPCStruct;
+import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.Version;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Hashtable;
+import java.util.Set;
+
+public class RpcConverter {
+
+ private static final String TAG = "RpcConverter";
+
+ private static final String RPC_PACKAGE = "com.smartdevicelink.proxy.rpc.";
+ private static final String RESPONSE_KEY = "Response";
+ private static final String GENERIC_RESPONSE_STRING = FunctionID.GENERIC_RESPONSE.toString();
+
+ public static RPCMessage extractRpc(ProtocolMessage message, Version protocolVersion){
+ Hashtable<String, Object> tempTable = convertProtocolMessage(message, protocolVersion);
+ if(tempTable != null){
+ try{
+ RPCMessage message1 = convertTableToRpc(tempTable);
+ if(message1 != null){
+ Log.v(TAG, "RPC type: " + message1.getClass().getCanonicalName());
+ }else{
+ Log.i(TAG, "Message was null");
+ }
+ return message1;
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ public static Hashtable<String, Object> convertProtocolMessage(ProtocolMessage message, Version protocolVersion){
+ Hashtable<String, Object> hash = new Hashtable<>();
+ if (protocolVersion!= null && protocolVersion.getMajor() > 1) {
+
+ Hashtable<String, Object> hashTemp = new Hashtable<>();
+ hashTemp.put(RPCMessage.KEY_CORRELATION_ID, message.getCorrID());
+ if (message.getJsonSize() > 0) {
+ final Hashtable<String, Object> mhash = JsonRPCMarshaller.unmarshall(message.getData());
+ //hashTemp.put(Names.parameters, mhash.get(Names.parameters));
+ if (mhash != null) {
+ hashTemp.put(RPCMessage.KEY_PARAMETERS, mhash);
+ }
+ }
+
+ // Log.d(TAG, "Function id: " + message.getFunctionID());
+
+ String functionName = FunctionID.getFunctionName(message.getFunctionID());
+ Log.d(TAG, "Function NAME: " + functionName);
+
+ if (functionName != null) {
+ hashTemp.put(RPCMessage.KEY_FUNCTION_NAME, functionName);
+ } else {
+ DebugTool.logWarning("Dispatch Incoming Message - function name is null unknown RPC. FunctionId: " + message.getFunctionID());
+ return null;
+ }
+ if (message.getRPCType() == 0x00) {
+ hash.put(RPCMessage.KEY_REQUEST, hashTemp);
+ } else if (message.getRPCType() == 0x01) {
+ hash.put(RPCMessage.KEY_RESPONSE, hashTemp);
+ } else if (message.getRPCType() == 0x02) {
+ hash.put(RPCMessage.KEY_NOTIFICATION, hashTemp);
+ }
+ if (message.getBulkData() != null) hash.put(RPCStruct.KEY_BULK_DATA, message.getBulkData());
+ if (message.getPayloadProtected()) hash.put(RPCStruct.KEY_PROTECTED, true);
+
+ return hash;
+ } else {
+ return JsonRPCMarshaller.unmarshall(message.getData());
+ }
+ }
+
+
+ public static RPCMessage convertTableToRpc(Hashtable<String,Object> rpcHashTable){
+
+ Hashtable<String,Object> params;
+ if(rpcHashTable.containsKey((RPCMessage.KEY_RESPONSE))){
+ params = (Hashtable)rpcHashTable.get((RPCMessage.KEY_RESPONSE));
+ }else if(rpcHashTable.containsKey((RPCMessage.KEY_NOTIFICATION))){
+ params = (Hashtable)rpcHashTable.get((RPCMessage.KEY_NOTIFICATION));
+ }else if(rpcHashTable.containsKey((RPCMessage.KEY_REQUEST))){
+ params = (Hashtable)rpcHashTable.get((RPCMessage.KEY_REQUEST));
+ }else{
+ Log.e(TAG, "Corrupted RPC table.");
+ return null;
+ }
+
+ if(params != null){
+ Set<String> keySet = params.keySet();
+ for(String key: keySet){
+ Log.i(TAG, key + " - " + params.get(key) );
+ }
+ }
+
+ if(params.containsKey(RPCMessage.KEY_FUNCTION_NAME)){
+ StringBuilder rpcClassName = new StringBuilder();
+ String functionName = (String)params.get(RPCMessage.KEY_FUNCTION_NAME);
+ rpcClassName.append(RPC_PACKAGE);
+ rpcClassName.append (functionName);
+
+ if(rpcHashTable.containsKey(RPCMessage.KEY_RESPONSE)
+ && !GENERIC_RESPONSE_STRING.equals(functionName)){
+ rpcClassName.append(RESPONSE_KEY);
+ }
+
+ Log.v(TAG, "Attempting to create " + rpcClassName.toString());
+ try {
+ Class rpcClass = Class.forName(rpcClassName.toString());
+ if(rpcClass != null){
+ java.lang.reflect.Constructor rpcConstructor = rpcClass.getConstructor(Hashtable.class);
+ if(rpcConstructor != null){
+ return (RPCMessage)rpcConstructor.newInstance(rpcHashTable);
+ }
+ }
+ } catch (ClassNotFoundException e) {
+ e.printStackTrace();
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InstantiationException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ } catch (ClassCastException e){
+ e.printStackTrace();
+ }
+ }else{
+ Log.w(TAG, "Unable to parse into RPC");
+ }
+
+ return null;
+ }
+
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/permission/BasePermissionManager.java b/base/src/main/java/com/smartdevicelink/managers/permission/BasePermissionManager.java
new file mode 100644
index 000000000..1a17b857f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/permission/BasePermissionManager.java
@@ -0,0 +1,350 @@
+package com.smartdevicelink.managers.permission;
+
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCNotification;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+import com.smartdevicelink.proxy.rpc.OnHMIStatus;
+import com.smartdevicelink.proxy.rpc.OnPermissionsChange;
+import com.smartdevicelink.proxy.rpc.PermissionItem;
+import com.smartdevicelink.proxy.rpc.enums.HMILevel;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ PermissionManager gives the developer information about what permissions are permitted in specific HMI level
+ and helps developers setup listeners to be called when specific permissions become allowed.<br>
+
+ This should be used through the {@link com.smartdevicelink.managers.SdlManager} and not be instantiated by itself
+**/
+
+abstract class BasePermissionManager extends BaseSubManager{
+
+ private HMILevel currentHMILevel;
+ private Map<FunctionID, PermissionItem> currentPermissionItems;
+ private OnRPCNotificationListener onHMIStatusListener, onPermissionsChangeListener;
+ private List<PermissionFilter> filters;
+
+ // Permission groups status constants
+ @IntDef({PERMISSION_GROUP_STATUS_ALLOWED, PERMISSION_GROUP_STATUS_DISALLOWED,
+ PERMISSION_GROUP_STATUS_MIXED, PERMISSION_GROUP_STATUS_UNKNOWN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionGroupStatus {}
+ public static final int PERMISSION_GROUP_STATUS_ALLOWED = 0; // Every permission in the group is currently allowed
+ public static final int PERMISSION_GROUP_STATUS_DISALLOWED = 1; // Every permission in the group is currently disallowed
+ public static final int PERMISSION_GROUP_STATUS_MIXED = 2; // Some permissions in the group are allowed and some disallowed
+ public static final int PERMISSION_GROUP_STATUS_UNKNOWN = 3; // The current status of the group is unknown
+
+ // Permission groups type constants
+ @IntDef({PERMISSION_GROUP_TYPE_ALL_ALLOWED, PERMISSION_GROUP_TYPE_ANY})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PermissionGroupType {}
+ public static final int PERMISSION_GROUP_TYPE_ALL_ALLOWED = 0; // Be notified when all of the permission in the group are allowed, or, when they all stop being allowed in some sense, that is, when they were all allowed, and now they are not.
+ public static final int PERMISSION_GROUP_TYPE_ANY = 1; // Be notified when any change in availability occurs among the group
+
+ /**
+ * Creates a new instance of the PermissionManager
+ * @param internalInterface
+ */
+ public BasePermissionManager(@NonNull ISdl internalInterface){
+ super(internalInterface);
+ this.currentPermissionItems = new HashMap<>();
+ this.filters = new ArrayList<>();
+
+ // Set PermissionManager's OnHMIStatusListener to keep currentHMILevel updated and call developer's listeners if needed
+ onHMIStatusListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+ HMILevel previousHMILevel = currentHMILevel;
+ currentHMILevel = ((OnHMIStatus)notification).getHmiLevel();
+ checkState();
+ notifyListeners(currentPermissionItems, previousHMILevel, currentPermissionItems, currentHMILevel);
+ }
+ };
+ internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, onHMIStatusListener);
+
+ // Set PermissionManager's PermissionsChangeListener to keep currentPermissionItems updated and call developer's listeners if needed
+ onPermissionsChangeListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+ List<PermissionItem> permissionItems = ((OnPermissionsChange)notification).getPermissionItem();
+ Map<FunctionID, PermissionItem> previousPermissionItems = currentPermissionItems;
+ currentPermissionItems = new HashMap<>();
+ for (PermissionItem permissionItem : permissionItems) {
+ currentPermissionItems.put(FunctionID.getEnumForString(permissionItem.getRpcName()), permissionItem);
+ }
+ notifyListeners(previousPermissionItems, currentHMILevel, currentPermissionItems, currentHMILevel);
+ previousPermissionItems.clear();
+ }
+ };
+ internalInterface.addOnRPCNotificationListener(FunctionID.ON_PERMISSIONS_CHANGE, onPermissionsChangeListener);
+ }
+
+ @Override
+ public void start(CompletionListener listener) {
+ checkState();
+ super.start(listener);
+ }
+
+ private synchronized void checkState(){
+ if(this.getState() == SETTING_UP && currentHMILevel != null){
+ transitionToState(READY);
+ }
+ }
+
+ /**
+ * Go over all developer's listeners and call them if needed because of HMI level change or permission items change
+ * @param previousPermissionItems
+ * @param previousHmiLevel
+ * @param currentPermissionItems
+ * @param currentHMILevel
+ */
+ private void notifyListeners(Map<FunctionID, PermissionItem> previousPermissionItems, HMILevel previousHmiLevel, Map<FunctionID, PermissionItem> currentPermissionItems, HMILevel currentHMILevel){
+ for (PermissionFilter filter : filters) {
+ boolean anyChange = false;
+ boolean allWereAllowed = true;
+ boolean allNowAllowed = true;
+ for (PermissionElement permissionElement : filter.getPermissionElements()) {
+ // If at any point this condition is satisfied, then we don't need to continue
+ if (anyChange && !allWereAllowed && !allNowAllowed){
+ break;
+ }
+ boolean rpcWasAllowed = isRPCAllowed(permissionElement.getRPCName(), previousPermissionItems, previousHmiLevel);
+ boolean rpcNowAllowed = isRPCAllowed(permissionElement.getRPCName(), currentPermissionItems, currentHMILevel);
+ if (rpcWasAllowed != rpcNowAllowed){
+ anyChange = true;
+ }
+ if (!rpcWasAllowed){
+ allWereAllowed = false;
+ }
+ if (!rpcNowAllowed){
+ allNowAllowed = false;
+ }
+ if (permissionElement.getParameters() != null && permissionElement.getParameters().size() > 0) {
+ for (String parameter : permissionElement.getParameters()) {
+ boolean parameterWasAllowed = isPermissionParameterAllowed(permissionElement.getRPCName(), parameter, previousPermissionItems, previousHmiLevel);
+ boolean parameterNowAllowed = isPermissionParameterAllowed(permissionElement.getRPCName(), parameter, currentPermissionItems, currentHMILevel);
+ if (parameterWasAllowed != parameterNowAllowed){
+ anyChange = true;
+ }
+ if (!parameterWasAllowed){
+ allWereAllowed = false;
+ }
+ if (!parameterNowAllowed){
+ allNowAllowed = false;
+ }
+ }
+ }
+ }
+ if (filter.getGroupType() == PERMISSION_GROUP_TYPE_ALL_ALLOWED && anyChange && (allWereAllowed || allNowAllowed)){
+ notifyListener(filter);
+ } else if (filter.getGroupType() == PERMISSION_GROUP_TYPE_ANY && anyChange){
+ notifyListener(filter);
+ }
+ }
+ }
+
+ /**
+ * Determine if an individual RPC is allowed
+ * @param rpcName FunctionID value that represents the name of the RPC
+ * @param permissionItems Map containing HMI and parameter permissions for a specific RPC
+ * @param hmiLevel If the RPC is allowed at that HMI Level. Ex: None or Full
+ * @return boolean represents whether the RPC is allowed or not
+ */
+ private boolean isRPCAllowed(@NonNull FunctionID rpcName, Map<FunctionID, PermissionItem> permissionItems, HMILevel hmiLevel){
+ PermissionItem permissionItem = permissionItems.get(rpcName);
+ if (hmiLevel == null || permissionItem == null || permissionItem.getHMIPermissions() == null || permissionItem.getHMIPermissions().getAllowed() == null){
+ return false;
+ } else if (permissionItem.getHMIPermissions().getUserDisallowed() != null){
+ return permissionItem.getHMIPermissions().getAllowed().contains(hmiLevel) && !permissionItem.getHMIPermissions().getUserDisallowed().contains(hmiLevel);
+ } else {
+ return permissionItem.getHMIPermissions().getAllowed().contains(hmiLevel);
+ }
+ }
+
+ /**
+ * Determine if an individual RPC is allowed for the current permission items and HMI level
+ * @param rpcName rpcName FunctionID value that represents the name of the RPC
+ * @return boolean represents whether the RPC is allowed or not
+ */
+ @SuppressWarnings("WeakerAccess")
+ public boolean isRPCAllowed(@NonNull FunctionID rpcName){
+ return isRPCAllowed(rpcName, currentPermissionItems, currentHMILevel);
+ }
+
+ /**
+ * Determine if an individual permission parameter is allowed
+ * @param rpcName FunctionID value that represents the name of the RPC
+ * @param parameter String value that represents a parameter for the RPC. Ex: "rpm" or "speed" for GetVehicleData
+ * @param permissionItems Map containing HMI and parameter permissions for a specific RPC
+ * @param hmiLevel If the RPC is allowed at that HMI Level. Ex: None or Full
+ * @return boolean represents whether the permission parameter is allowed or not
+ */
+ private boolean isPermissionParameterAllowed(@NonNull FunctionID rpcName, @NonNull String parameter, Map<FunctionID, PermissionItem> permissionItems, HMILevel hmiLevel){
+ PermissionItem permissionItem = permissionItems.get(rpcName);
+ if (!isRPCAllowed(rpcName, permissionItems, hmiLevel) || permissionItem.getParameterPermissions() == null || permissionItem.getParameterPermissions().getAllowed() == null){
+ return false;
+ } else if (permissionItem.getParameterPermissions().getUserDisallowed() != null){
+ return permissionItem.getParameterPermissions().getAllowed().contains(parameter) && !permissionItem.getParameterPermissions().getUserDisallowed().contains(parameter);
+ } else {
+ return permissionItem.getParameterPermissions().getAllowed().contains(parameter);
+ }
+ }
+
+ /**
+ * Determine if an individual permission parameter is allowed for current permission items and current HMI level
+ * @param rpcName FunctionID value that represents the name of the RPC
+ * @param parameter String value that represents a parameter for the RPC
+ * @return boolean represents whether the permission parameter is allowed or not
+ */
+ @SuppressWarnings("WeakerAccess")
+ public boolean isPermissionParameterAllowed(@NonNull FunctionID rpcName, @NonNull String parameter){
+ return isPermissionParameterAllowed(rpcName, parameter, currentPermissionItems, currentHMILevel);
+ }
+
+ /**
+ * Clean up everything after the manager is no longer needed
+ */
+ @Override
+ public void dispose(){
+ super.dispose();
+
+ // Remove onHMIStatusListener
+ internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, onHMIStatusListener);
+ onHMIStatusListener = null;
+
+ // Remove onPermissionsChangeListener
+ internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, onPermissionsChangeListener);
+ onPermissionsChangeListener = null;
+
+ // Remove developer's listeners
+ filters.clear();
+ }
+
+ /**
+ * Determine if a group of permissions is allowed for the current HMI level
+ * @param permissionElements list of PermissionElement that represents the RPC names and their parameters
+ * @return PermissionGroupStatus int value that gives an overall view whether the permissions are allowed or not
+ * @see PermissionGroupStatus
+ */
+ @SuppressWarnings("WeakerAccess")
+ public @PermissionGroupStatus int getGroupStatusOfPermissions(@NonNull List<PermissionElement> permissionElements){
+ if (currentHMILevel == null){
+ return PERMISSION_GROUP_STATUS_UNKNOWN;
+ }
+
+ boolean hasAllowed = false;
+ boolean hasDisallowed = false;
+
+ for (PermissionElement permissionElement : permissionElements) {
+ // If at any point, we have both allowed and disallowed permissions, return the mixed result
+ if (hasAllowed && hasDisallowed) {
+ return PERMISSION_GROUP_STATUS_MIXED;
+ }
+
+ if (permissionElement == null){
+ continue;
+ } else if (!isRPCAllowed(permissionElement.getRPCName())){
+ hasDisallowed = true;
+ } else {
+ if (permissionElement.getParameters() == null || permissionElement.getParameters().size() == 0){
+ hasAllowed = true;
+ } else {
+ for (String permissionParameter : permissionElement.getParameters()) {
+ if (isPermissionParameterAllowed(permissionElement.getRPCName(), permissionParameter)) {
+ hasAllowed = true;
+ } else {
+ hasDisallowed = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!hasAllowed && !hasDisallowed){
+ return PERMISSION_GROUP_STATUS_ALLOWED;
+ } else if (hasAllowed && hasDisallowed) {
+ return PERMISSION_GROUP_STATUS_MIXED;
+ } else if (hasAllowed) {
+ return PERMISSION_GROUP_STATUS_ALLOWED;
+ } else{
+ return PERMISSION_GROUP_STATUS_DISALLOWED;
+ }
+ }
+
+ /**
+ * Determine if a group of permissions is allowed for the current HMI level
+ * This method is similar to getGroupStatusOfPermissions() but returns more detailed result about each individual permission
+ * @param permissionElements list of PermissionElement that represents the RPC names and their parameters
+ * @return a map with keys that are the passed in RPC names specifying if that RPC and its parameter permissions are currently allowed for the current HMI level
+ */
+ @SuppressWarnings("WeakerAccess")
+ public Map <FunctionID, PermissionStatus> getStatusOfPermissions(@NonNull List<PermissionElement> permissionElements){
+ Map<FunctionID, PermissionStatus> statusOfPermissions = new HashMap<>();
+ for (PermissionElement permissionElement : permissionElements) {
+ if (permissionElement == null){
+ continue;
+ }
+ Map<String, Boolean> allowedParameters = null;
+ if (permissionElement.getParameters() != null && permissionElement.getParameters().size() > 0) {
+ allowedParameters = new HashMap<>();
+ for (String permissionParameter : permissionElement.getParameters()) {
+ allowedParameters.put(permissionParameter, isPermissionParameterAllowed(permissionElement.getRPCName(), permissionParameter));
+ }
+ }
+ PermissionStatus permissionStatus = new PermissionStatus(permissionElement.getRPCName(), isRPCAllowed(permissionElement.getRPCName()), allowedParameters);
+ statusOfPermissions.put(permissionElement.getRPCName(), permissionStatus);
+ }
+ return statusOfPermissions;
+ }
+
+ /**
+ * Call the listener for a specific filter
+ * @param filter the permission filter to cal
+ */
+ private void notifyListener(@NonNull PermissionFilter filter){
+ int permissionGroupStatus = getGroupStatusOfPermissions(filter.getPermissionElements());
+ Map <FunctionID, PermissionStatus> allowedPermissions = getStatusOfPermissions(filter.getPermissionElements());
+ filter.getListener().onPermissionsChange(allowedPermissions, permissionGroupStatus);
+ }
+
+ /**
+ * Add a listener to be called when there is permissions change
+ * @param permissionElements list of PermissionElement that represents the RPC names and their parameters
+ * @param groupType PermissionGroupType int value represents whether we need the listener to be called when there is any permissions change or only when all permission become allowed
+ * @param listener OnPermissionChangeListener interface
+ * @return unique uuid number for the listener. It can be used to remove the listener later.
+ */
+ @SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
+ public UUID addListener(@NonNull List<PermissionElement> permissionElements, @PermissionGroupType int groupType, @NonNull OnPermissionChangeListener listener){
+ PermissionFilter filter = new PermissionFilter(null, permissionElements, groupType, listener);
+ filters.add(filter);
+ return filter.getIdentifier();
+ }
+
+ /**
+ * Removes specific listener
+ * @param listenerId the id of the listener
+ */
+ @SuppressWarnings("WeakerAccess")
+ public void removeListener(@NonNull UUID listenerId){
+ for (PermissionFilter filter : filters) {
+ if (filter.getIdentifier().equals(listenerId)) {
+ filters.remove(filter);
+ break;
+ }
+ }
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/permission/OnPermissionChangeListener.java b/base/src/main/java/com/smartdevicelink/managers/permission/OnPermissionChangeListener.java
new file mode 100644
index 000000000..4f60d85d3
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/permission/OnPermissionChangeListener.java
@@ -0,0 +1,20 @@
+package com.smartdevicelink.managers.permission;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+
+import java.util.Map;
+
+/**
+ * OnPermissionChangeListener is a listener which includes a callback method that can be called when there are permission changes
+ */
+public interface OnPermissionChangeListener {
+ /**
+ * Call back method that PermissionManager will call to inform the developer about permission changes
+ * @param allowedPermissions an overall view about the status of the permissions
+ * @param permissionGroupStatus a detailed view about which permissions are allowed and which ones are not
+ * @see PermissionManager.PermissionGroupStatus
+ */
+ void onPermissionsChange(@NonNull Map<FunctionID, PermissionStatus> allowedPermissions, @NonNull @PermissionManager.PermissionGroupStatus int permissionGroupStatus);
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/permission/PermissionElement.java b/base/src/main/java/com/smartdevicelink/managers/permission/PermissionElement.java
new file mode 100644
index 000000000..edb3788b6
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/permission/PermissionElement.java
@@ -0,0 +1,42 @@
+package com.smartdevicelink.managers.permission;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+
+import java.util.List;
+
+/**
+ * PermissionElement holds the RPC name that the developer wants to add a listener for.
+ * It also holds any permission parameters for that RPC that the developer wants to track as well.
+ */
+public class PermissionElement {
+ private final FunctionID rpcName;
+ private final List<String> parameters;
+
+ /**
+ * Create a new instance of PermissionElement
+ * @param rpcName
+ * @param parameters
+ */
+ public PermissionElement(@NonNull FunctionID rpcName, List<String> parameters){
+ this.rpcName = rpcName;
+ this.parameters = parameters;
+ }
+
+ /**
+ * Get the RPC name
+ * @return FunctionID value represents the RPC name
+ */
+ public FunctionID getRPCName() {
+ return rpcName;
+ }
+
+ /**
+ * Get the permission parameters for the RPC
+ * @return List<String> represents the permission parameters for the RPC
+ */
+ public List<String> getParameters() {
+ return parameters;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/permission/PermissionFilter.java b/base/src/main/java/com/smartdevicelink/managers/permission/PermissionFilter.java
new file mode 100644
index 000000000..de7aab01a
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/permission/PermissionFilter.java
@@ -0,0 +1,70 @@
+package com.smartdevicelink.managers.permission;
+
+import android.support.annotation.NonNull;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * PermissionFilter holds all the required information for a specific OnPermissionChangeListener
+ */
+class PermissionFilter {
+ private final UUID identifier;
+ private final List<PermissionElement> permissionElements;
+ private final int groupType;
+ private final OnPermissionChangeListener listener;
+
+ /**
+ * Creates a new instance of PermissionFilter
+ * @param identifier
+ * @param permissionElements
+ * @param groupType
+ * @param listener
+ * @see com.smartdevicelink.managers.permission.PermissionManager.PermissionGroupType
+ */
+ PermissionFilter(UUID identifier, @NonNull List<PermissionElement> permissionElements, @NonNull @PermissionManager.PermissionGroupType int groupType, @NonNull OnPermissionChangeListener listener) {
+ if (identifier == null) {
+ this.identifier = UUID.randomUUID();
+ } else {
+ this.identifier = identifier;
+ }
+ this.permissionElements = permissionElements;
+ this.groupType = groupType;
+ this.listener = listener;
+ }
+
+ /**
+ * Get the unique id for the listener
+ * @return UUID object represents the id for the listener
+ */
+ protected UUID getIdentifier() {
+ return identifier;
+ }
+
+ /**
+ * Get the permission elements that the developer wants to add a listener for
+ * @return List<PermissionElement> represents the RPCs and their parameters that the developer wants to add a listener for
+ */
+ protected List<PermissionElement> getPermissionElements() {
+ return permissionElements;
+ }
+
+ /**
+ * Get how we want the listener to be called: when any change happens? or when all permissions become allowed?
+ * @return PermissionGroupType int value represents whether the developer needs the listener to be called when there is any permissions change or only when all permission become allowed
+ * @see com.smartdevicelink.managers.permission.PermissionManager.PermissionGroupType
+ */
+ protected @PermissionManager.PermissionGroupType int getGroupType() {
+ return groupType;
+ }
+
+ /**
+ * Get the listener object
+ * @return OnPermissionChangeListener object represents the listener for that filter
+ */
+ protected OnPermissionChangeListener getListener() {
+ return listener;
+ }
+
+
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/permission/PermissionStatus.java b/base/src/main/java/com/smartdevicelink/managers/permission/PermissionStatus.java
new file mode 100644
index 000000000..11f464552
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/permission/PermissionStatus.java
@@ -0,0 +1,68 @@
+package com.smartdevicelink.managers.permission;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+
+import java.util.Map;
+
+/**
+ * PermissionStatus gives a detailed view about whether an RPC and its permission parameters are allowed or not
+ */
+public class PermissionStatus {
+ private final FunctionID rpcName;
+ private boolean isRPCAllowed;
+ private Map<String, Boolean> allowedParameters;
+
+ /**
+ * Creates a new PermissionStatus instance
+ * @param rpcName
+ * @param isRPCAllowed
+ * @param allowedParameters
+ */
+ public PermissionStatus(@NonNull FunctionID rpcName, @NonNull boolean isRPCAllowed, Map<String, Boolean> allowedParameters) {
+ this.rpcName = rpcName;
+ this.isRPCAllowed = isRPCAllowed;
+ this.allowedParameters = allowedParameters;
+ }
+
+ /**
+ * Get the name of the RPC
+ * @return FunctionID value represents the name of the RPC
+ */
+ public FunctionID getRPCName() {
+ return rpcName;
+ }
+
+ /**
+ * Get whether the RCP is allowed or not
+ * @return boolean represents whether the RCP is allowed or not
+ */
+ public boolean getIsRPCAllowed() {
+ return isRPCAllowed;
+ }
+
+ /**
+ * Set whether the RPC is allowed or not
+ * @param isRPCAllowed
+ */
+ protected void setIsRPCAllowed(@NonNull boolean isRPCAllowed) {
+ this.isRPCAllowed = isRPCAllowed;
+ }
+
+ /**
+ * Get the status of the permission parameter for the RPC
+ * @return Map<String, Boolean> object with keys that represent the permission parameter names and values that represent whether the parameters are allowed or not
+ */
+ public Map<String, Boolean> getAllowedParameters() {
+ return allowedParameters;
+ }
+
+ /**
+ * Set the status of the permission parameter for the RPC
+ * @param allowedParameters
+ */
+ protected void setAllowedParameters(Map<String, Boolean> allowedParameters) {
+ this.allowedParameters = allowedParameters;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java
new file mode 100644
index 000000000..df8e65837
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseScreenManager.java
@@ -0,0 +1,358 @@
+package com.smartdevicelink.managers.screen;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+import com.smartdevicelink.proxy.rpc.enums.MetadataType;
+import com.smartdevicelink.proxy.rpc.enums.TextAlignment;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+/**
+ * <strong>ScreenManager</strong> <br>
+ *
+ * Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br>
+*/
+abstract public class BaseScreenManager extends BaseSubManager {
+
+ private static String TAG = "ScreenManager";
+ private final WeakReference<FileManager> fileManager;
+ private SoftButtonManager softButtonManager;
+ private TextAndGraphicManager textAndGraphicManager;
+
+ // Sub manager listener
+ private final CompletionListener subManagerListener = new CompletionListener() {
+ @Override
+ public synchronized void onComplete(boolean success) {
+ if (softButtonManager != null && textAndGraphicManager != null) {
+ if (softButtonManager.getState() == BaseSubManager.READY && textAndGraphicManager.getState() == BaseSubManager.READY) {
+ DebugTool.logInfo("Starting screen manager, all sub managers are in ready state");
+ transitionToState(READY);
+ } else if (softButtonManager.getState() == BaseSubManager.ERROR && textAndGraphicManager.getState() == BaseSubManager.ERROR) {
+ Log.e(TAG, "ERROR starting screen manager, both sub managers in error state");
+ transitionToState(ERROR);
+ } else if (textAndGraphicManager.getState() == BaseSubManager.SETTING_UP || softButtonManager.getState() == BaseSubManager.SETTING_UP) {
+ DebugTool.logInfo("SETTING UP screen manager, one sub manager is still setting up");
+ transitionToState(SETTING_UP);
+ } else {
+ Log.w(TAG, "LIMITED starting screen manager, one sub manager in error state and the other is ready");
+ transitionToState(LIMITED);
+ }
+ } else {
+ // We should never be here, but somehow one of the sub-sub managers is null
+ Log.e(TAG, "ERROR one of the screen sub managers is null");
+ transitionToState(ERROR);
+ }
+ }
+ };
+
+ public BaseScreenManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) {
+ super(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ initialize();
+ }
+
+ @Override
+ public void start(CompletionListener listener) {
+ super.start(listener);
+ this.softButtonManager.start(subManagerListener);
+ this.textAndGraphicManager.start(subManagerListener);
+ }
+
+ private void initialize(){
+ if (fileManager.get() != null) {
+ this.softButtonManager = new SoftButtonManager(internalInterface, fileManager.get());
+ this.textAndGraphicManager = new TextAndGraphicManager(internalInterface, fileManager.get(), softButtonManager);
+ }
+ }
+
+ /**
+ * <p>Called when manager is being torn down</p>
+ */
+ @Override
+ public void dispose() {
+ softButtonManager.dispose();
+ textAndGraphicManager.dispose();
+ super.dispose();
+ }
+
+ /**
+ * Set the textField1 on the head unit screen
+ * Sending an empty String "" will clear the field
+ * @param textField1 String value represents the textField1
+ */
+ public void setTextField1(String textField1) {
+ this.softButtonManager.setCurrentMainField1(textField1);
+ this.textAndGraphicManager.setTextField1(textField1);
+ }
+
+ /**
+ * Get the current textField1 value
+ * @return a String value represents the current textField1 value
+ */
+ public String getTextField1() {
+ return this.textAndGraphicManager.getTextField1();
+ }
+
+ /**
+ * Set the textField2 on the head unit screen
+ * Sending an empty String "" will clear the field
+ * @param textField2 String value represents the textField1
+ */
+ public void setTextField2(String textField2) {
+ this.textAndGraphicManager.setTextField2(textField2);
+ }
+
+ /**
+ * Get the current textField2 value
+ * @return a String value represents the current textField2 value
+ */
+ public String getTextField2() {
+ return this.textAndGraphicManager.getTextField2();
+ }
+
+ /**
+ * Set the textField3 on the head unit screen
+ * Sending an empty String "" will clear the field
+ * @param textField3 String value represents the textField1
+ */
+ public void setTextField3(String textField3) {
+ this.textAndGraphicManager.setTextField3(textField3);
+ }
+
+ /**
+ * Get the current textField3 value
+ * @return a String value represents the current textField3 value
+ */
+ public String getTextField3() {
+ return this.textAndGraphicManager.getTextField3();
+ }
+
+ /**
+ * Set the textField4 on the head unit screen
+ * Sending an empty String "" will clear the field
+ * @param textField4 String value represents the textField1
+ */
+ public void setTextField4(String textField4) {
+ this.textAndGraphicManager.setTextField4(textField4);
+ }
+
+ /**
+ * Get the current textField4 value
+ * @return a String value represents the current textField4 value
+ */
+ public String getTextField4() {
+ return this.textAndGraphicManager.getTextField4();
+ }
+
+ /**
+ * Set the mediaTrackTextField on the head unit screen
+ * @param mediaTrackTextField String value represents the mediaTrackTextField
+ */
+ public void setMediaTrackTextField(String mediaTrackTextField) {
+ this.textAndGraphicManager.setMediaTrackTextField(mediaTrackTextField);
+ }
+
+ /**
+ * Get the current mediaTrackTextField value
+ * @return a String value represents the current mediaTrackTextField
+ */
+ public String getMediaTrackTextField() {
+ return this.textAndGraphicManager.getMediaTrackTextField();
+ }
+
+ /**
+ * Set the primaryGraphic on the head unit screen
+ * @param primaryGraphic an SdlArtwork object represents the primaryGraphic
+ */
+ public void setPrimaryGraphic(SdlArtwork primaryGraphic) {
+ if (primaryGraphic == null){
+ primaryGraphic = textAndGraphicManager.getBlankArtwork();
+ }
+ this.textAndGraphicManager.setPrimaryGraphic(primaryGraphic);
+ }
+
+ /**
+ * Get the current primaryGraphic value
+ * @return an SdlArtwork object represents the current primaryGraphic
+ */
+ public SdlArtwork getPrimaryGraphic() {
+ return this.textAndGraphicManager.getPrimaryGraphic();
+ }
+
+ /**
+ * Set the secondaryGraphic on the head unit screen
+ * @param secondaryGraphic an SdlArtwork object represents the secondaryGraphic
+ */
+ public void setSecondaryGraphic(SdlArtwork secondaryGraphic) {
+ if (secondaryGraphic == null){
+ secondaryGraphic = textAndGraphicManager.getBlankArtwork();
+ }
+ this.textAndGraphicManager.setSecondaryGraphic(secondaryGraphic);
+ }
+
+ /**
+ * Get the current secondaryGraphic value
+ * @return an SdlArtwork object represents the current secondaryGraphic
+ */
+ public SdlArtwork getSecondaryGraphic() {
+ return this.textAndGraphicManager.getSecondaryGraphic();
+ }
+
+ /**
+ * Set the alignment for the text fields
+ * @param textAlignment TextAlignment value represents the alignment for the text fields
+ */
+ public void setTextAlignment(TextAlignment textAlignment) {
+ this.textAndGraphicManager.setTextAlignment(textAlignment);
+ }
+
+ /**
+ * Get the alignment for the text fields
+ * @return a TextAlignment value represents the alignment for the text fields
+ */
+ public TextAlignment getTextAlignment() {
+ return this.textAndGraphicManager.getTextAlignment();
+ }
+
+ /**
+ * Set the metadata type for the textField1
+ * @param textField1Type a MetadataType value represents the metadata for textField1
+ */
+ public void setTextField1Type(MetadataType textField1Type) {
+ this.textAndGraphicManager.setTextField1Type(textField1Type);
+ }
+
+ /**
+ * Get the metadata type for textField1
+ * @return a MetadataType value represents the metadata for textField1
+ */
+ public MetadataType getTextField1Type() {
+ return this.textAndGraphicManager.getTextField1Type();
+ }
+
+ /**
+ * Set the metadata type for the textField2
+ * @param textField2Type a MetadataType value represents the metadata for textField2
+ */
+ public void setTextField2Type(MetadataType textField2Type) {
+ this.textAndGraphicManager.setTextField2Type(textField2Type);
+ }
+
+ /**
+ * Get the metadata type for textField2
+ * @return a MetadataType value represents the metadata for textField2
+ */
+ public MetadataType getTextField2Type() {
+ return this.textAndGraphicManager.getTextField2Type();
+ }
+
+ /**
+ * Set the metadata type for the textField3
+ * @param textField3Type a MetadataType value represents the metadata for textField3
+ */
+ public void setTextField3Type(MetadataType textField3Type) {
+ this.textAndGraphicManager.setTextField3Type(textField3Type);
+ }
+
+ /**
+ * Get the metadata type for textField3
+ * @return a MetadataType value represents the metadata for textField3
+ */
+ public MetadataType getTextField3Type() {
+ return this.textAndGraphicManager.getTextField3Type();
+ }
+
+ /**
+ * Set the metadata type for the textField4
+ * @param textField4Type a MetadataType value represents the metadata for textField4
+ */
+ public void setTextField4Type(MetadataType textField4Type) {
+ this.textAndGraphicManager.setTextField4Type(textField4Type);
+ }
+
+ /**
+ * Get the metadata type for textField4
+ * @return a MetadataType value represents the metadata for textField4
+ */
+ public MetadataType getTextField4Type() {
+ return this.textAndGraphicManager.getTextField4Type();
+ }
+
+ /**
+ * Set softButtonObjects list and upload the images to the head unit
+ * @param softButtonObjects the list of the SoftButtonObject values that should be displayed on the head unit
+ */
+ public void setSoftButtonObjects(@NonNull List<SoftButtonObject> softButtonObjects) {
+ softButtonManager.setSoftButtonObjects(softButtonObjects);
+ }
+
+ /**
+ * Get the soft button objects list
+ * @return a List<SoftButtonObject>
+ */
+ public List<SoftButtonObject> getSoftButtonObjects() {
+ return softButtonManager.getSoftButtonObjects();
+ }
+
+ /**
+ * Get the SoftButtonObject that has the provided name
+ * @param name a String value that represents the name
+ * @return a SoftButtonObject
+ */
+ public SoftButtonObject getSoftButtonObjectByName(@NonNull String name){
+ return softButtonManager.getSoftButtonObjectByName(name);
+ }
+
+ /**
+ * Get the SoftButtonObject that has the provided buttonId
+ * @param buttonId a int value that represents the id of the button
+ * @return a SoftButtonObject
+ */
+ public SoftButtonObject getSoftButtonObjectById(int buttonId){
+ return softButtonManager.getSoftButtonObjectById(buttonId);
+ }
+
+ /**
+ * Begin a multiple updates transaction. The updates will be applied when commit() is called<br>
+ * Note: if we don't use beginTransaction & commit, every update will be sent individually.
+ */
+ public void beginTransaction(){
+ softButtonManager.setBatchUpdates(true);
+ textAndGraphicManager.setBatchUpdates(true);
+ }
+
+ /**
+ * Send the updates that were started after beginning the transaction
+ * @param listener a CompletionListener that has a callback that will be called when the updates are finished
+ */
+ public void commit(final CompletionListener listener){
+ softButtonManager.setBatchUpdates(false);
+ softButtonManager.update(new CompletionListener() {
+ boolean updateSuccessful = true;
+ @Override
+ public void onComplete(boolean success) {
+ if (!success){
+ updateSuccessful = false;
+ }
+ textAndGraphicManager.setBatchUpdates(false);
+ textAndGraphicManager.update(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (!success){
+ updateSuccessful = false;
+ }
+ listener.onComplete(updateSuccessful);
+ }
+ });
+ }
+ });
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
new file mode 100644
index 000000000..e44c1ba2c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseSoftButtonManager.java
@@ -0,0 +1,568 @@
+package com.smartdevicelink.managers.screen;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCNotification;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener;
+import com.smartdevicelink.proxy.rpc.DisplayCapabilities;
+import com.smartdevicelink.proxy.rpc.OnButtonEvent;
+import com.smartdevicelink.proxy.rpc.OnButtonPress;
+import com.smartdevicelink.proxy.rpc.OnHMIStatus;
+import com.smartdevicelink.proxy.rpc.Show;
+import com.smartdevicelink.proxy.rpc.SoftButton;
+import com.smartdevicelink.proxy.rpc.SoftButtonCapabilities;
+import com.smartdevicelink.proxy.rpc.enums.ButtonName;
+import com.smartdevicelink.proxy.rpc.enums.HMILevel;
+import com.smartdevicelink.proxy.rpc.enums.Result;
+import com.smartdevicelink.proxy.rpc.enums.SoftButtonType;
+import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * <strong>SoftButtonManager</strong> <br>
+ * SoftButtonManager gives the developer the ability to control how soft buttons are displayed on the head unit.<br>
+ * Note: This class must be accessed through the SdlManager->ScreenManager. Do not instantiate it by itself.<br>
+ */
+abstract class BaseSoftButtonManager extends BaseSubManager {
+
+ private static final String TAG = "SoftButtonManager";
+ private WeakReference<FileManager> fileManager;
+ private DisplayCapabilities displayCapabilities;
+ private SoftButtonCapabilities softButtonCapabilities;
+ private CopyOnWriteArrayList<SoftButtonObject> softButtonObjects;
+ private HMILevel currentHMILevel;
+ private Show inProgressShowRPC;
+ private CompletionListener inProgressListener, queuedUpdateListener, cachedListener;
+ private boolean hasQueuedUpdate, batchUpdates, waitingOnHMILevelUpdateToSetButtons;
+ private final OnSystemCapabilityListener onSoftButtonCapabilitiesListener, onDisplayCapabilitiesListener;
+ private final OnRPCNotificationListener onHMIStatusListener, onButtonPressListener, onButtonEventListener;
+ private final SoftButtonObject.UpdateListener updateListener;
+
+ /**
+ * HAX: This is necessary due to a Ford Sync 3 bug that doesn't like Show requests without a main field being set (it will accept them, but with a GENERIC_ERROR, and 10-15 seconds late...)
+ */
+ private String currentMainField1;
+
+
+ /**
+ * Creates a new instance of the SoftButtonManager
+ * @param internalInterface
+ * @param fileManager
+ */
+ BaseSoftButtonManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager) {
+ super(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ this.softButtonObjects = new CopyOnWriteArrayList<>();
+ this.currentHMILevel = HMILevel.HMI_NONE; // Assume NONE until we get something else
+ this.waitingOnHMILevelUpdateToSetButtons = false;
+ this.updateListener = new SoftButtonObject.UpdateListener() {
+ @Override
+ public void onUpdate() {
+ update(null);
+ }
+ };
+
+
+ // Add OnSoftButtonCapabilitiesListener to keep softButtonCapabilities updated
+ onSoftButtonCapabilitiesListener = new OnSystemCapabilityListener() {
+ @Override
+ public void onCapabilityRetrieved(Object capability) {
+ List<SoftButtonCapabilities> softButtonCapabilitiesList = (List<SoftButtonCapabilities>) capability;
+ if (softButtonCapabilitiesList != null && !softButtonCapabilitiesList.isEmpty()) {
+ softButtonCapabilities = softButtonCapabilitiesList.get(0);
+ } else {
+ softButtonCapabilities = null;
+ }
+ }
+
+ @Override
+ public void onError(String info) {
+ Log.w(TAG, "SoftButton Capability cannot be retrieved:");
+ softButtonCapabilities = null;
+ }
+ };
+ this.internalInterface.addOnSystemCapabilityListener(SystemCapabilityType.SOFTBUTTON, onSoftButtonCapabilitiesListener);
+
+
+ // Add OnDisplayCapabilitiesListener to keep displayCapabilities updated
+ onDisplayCapabilitiesListener = new OnSystemCapabilityListener() {
+ @Override
+ public void onCapabilityRetrieved(Object capability) {
+ displayCapabilities = (DisplayCapabilities) capability;
+ }
+
+ @Override
+ public void onError(String info) {
+ Log.w(TAG, "Display Capability cannot be retrieved:");
+ displayCapabilities = null;
+ }
+ };
+ this.internalInterface.addOnSystemCapabilityListener(SystemCapabilityType.DISPLAY, onDisplayCapabilitiesListener);
+
+
+ // Add OnHMIStatusListener to keep currentHMILevel updated
+ this.onHMIStatusListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+
+ OnHMIStatus onHMIStatus = (OnHMIStatus) notification;
+ HMILevel oldHmiLevel = currentHMILevel;
+ currentHMILevel = onHMIStatus.getHmiLevel();
+
+
+ // Auto-send an updated show if we were in NONE and now we are not
+ if (oldHmiLevel == HMILevel.HMI_NONE && currentHMILevel != HMILevel.HMI_NONE) {
+ if (waitingOnHMILevelUpdateToSetButtons) {
+ setSoftButtonObjects(softButtonObjects);
+ } else {
+ update(cachedListener);
+ }
+ }
+ }
+ };
+ this.internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, onHMIStatusListener);
+
+
+ // Add OnButtonPressListener to notify SoftButtonObjects when there is a button press
+ this.onButtonPressListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+ OnButtonPress onButtonPress = (OnButtonPress) notification;
+ if (onButtonPress!= null && onButtonPress.getButtonName() == ButtonName.CUSTOM_BUTTON) {
+ Integer buttonId = onButtonPress.getCustomButtonName();
+ if (getSoftButtonObjects() != null) {
+ for (SoftButtonObject softButtonObject : getSoftButtonObjects()) {
+ if (softButtonObject.getButtonId() == buttonId && softButtonObject.getOnEventListener() != null) {
+ softButtonObject.getOnEventListener().onPress(getSoftButtonObjectById(buttonId), onButtonPress);
+ break;
+ }
+ }
+ }
+ }
+ }
+ };
+ this.internalInterface.addOnRPCNotificationListener(FunctionID.ON_BUTTON_PRESS, onButtonPressListener);
+
+
+ // Add OnButtonEventListener to notify SoftButtonObjects when there is a button event
+ this.onButtonEventListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+ OnButtonEvent onButtonEvent = (OnButtonEvent) notification;
+ if (onButtonEvent!= null && onButtonEvent.getButtonName() == ButtonName.CUSTOM_BUTTON) {
+ Integer buttonId = onButtonEvent.getCustomButtonID();
+ if (getSoftButtonObjects() != null) {
+ for (SoftButtonObject softButtonObject : getSoftButtonObjects()) {
+ if (softButtonObject.getButtonId() == buttonId && softButtonObject.getOnEventListener() != null) {
+ softButtonObject.getOnEventListener().onEvent(getSoftButtonObjectById(buttonId), onButtonEvent);
+ break;
+ }
+ }
+ }
+ }
+ }
+ };
+ this.internalInterface.addOnRPCNotificationListener(FunctionID.ON_BUTTON_EVENT, onButtonEventListener);
+ }
+
+ @Override
+ public void start(CompletionListener listener) {
+ transitionToState(READY);
+ super.start(listener);
+ }
+
+ /**
+ * Get the SoftButtonObject that has the provided name
+ * @param name a String value that represents the name
+ * @return a SoftButtonObject
+ */
+ protected SoftButtonObject getSoftButtonObjectByName(String name) {
+ for (SoftButtonObject softButtonObject : softButtonObjects) {
+ if (softButtonObject.getName().equals(name)) {
+ return softButtonObject;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the SoftButtonObject that has the provided buttonId
+ * @param buttonId a int value that represents the id of the button
+ * @return a SoftButtonObject
+ */
+ protected SoftButtonObject getSoftButtonObjectById(int buttonId) {
+ for (SoftButtonObject softButtonObject : softButtonObjects) {
+ if (softButtonObject.getButtonId() == buttonId) {
+ return softButtonObject;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the soft button objects list
+ * @return a List<SoftButtonObject>
+ */
+ protected List<SoftButtonObject> getSoftButtonObjects() {
+ return softButtonObjects;
+ }
+
+ /**
+ * Set softButtonObjects list and upload the images to the head unit
+ * @param list the list of the SoftButtonObject values that should be displayed on the head unit
+ */
+ protected void setSoftButtonObjects(@NonNull List<SoftButtonObject> list) {
+ // Convert the List to CopyOnWriteArrayList
+ CopyOnWriteArrayList<SoftButtonObject> softButtonObjects;
+ if(list instanceof CopyOnWriteArrayList){
+ softButtonObjects = (CopyOnWriteArrayList<SoftButtonObject>) list;
+ }else{
+ softButtonObjects = new CopyOnWriteArrayList<>(list);
+ }
+
+
+ if (hasTwoSoftButtonObjectsOfSameName(softButtonObjects)) {
+ this.softButtonObjects = new CopyOnWriteArrayList<>();
+ Log.e(TAG, "Attempted to set soft button objects, but two buttons had the same name");
+ return;
+ }
+
+ // Set ids and updateListeners for soft button objects
+ for (int i = 0; i < softButtonObjects.size(); i++) {
+ softButtonObjects.get(i).setButtonId(i * 100);
+ softButtonObjects.get(i).setUpdateListener(updateListener);
+ }
+ this.softButtonObjects = softButtonObjects;
+
+
+ if (currentHMILevel == null || currentHMILevel == HMILevel.HMI_NONE) {
+ waitingOnHMILevelUpdateToSetButtons = true;
+ return;
+ }
+
+
+ // End any in-progress update
+ inProgressShowRPC = null;
+ if (inProgressListener != null) {
+ inProgressListener.onComplete(false);
+ inProgressListener = null;
+ }
+
+
+ // End any queued update
+ hasQueuedUpdate = false;
+ if (queuedUpdateListener != null) {
+ queuedUpdateListener.onComplete(false);
+ queuedUpdateListener = null;
+ }
+
+
+ // Prepare soft button images to be uploaded to the head unit.
+ // we will prepare a list for initial state images and another list for other state images
+ // so we can upload the initial state images first, then the other states images.
+ List<SdlArtwork> initialStatesToBeUploaded = new ArrayList<>();
+ List<SdlArtwork> otherStatesToBeUploaded = new ArrayList<>();
+ if (softButtonImagesSupported() && fileManager.get() != null) {
+ for (SoftButtonObject softButtonObject : softButtonObjects) {
+ SoftButtonState initialState = null;
+ if (softButtonObject != null) {
+ initialState = softButtonObject.getCurrentState();
+ }
+ if (initialState != null && softButtonObject.getStates() != null) {
+ for (SoftButtonState softButtonState : softButtonObject.getStates()) {
+ if (softButtonState != null && softButtonState.getName() != null && softButtonState.getArtwork() != null && !fileManager.get().hasUploadedFile(softButtonState.getArtwork())) {
+ if (softButtonState.getName().equals(initialState.getName())) {
+ initialStatesToBeUploaded.add(softButtonObject.getCurrentState().getArtwork());
+ } else{
+ otherStatesToBeUploaded.add(softButtonState.getArtwork());
+ }
+ }
+ }
+ }
+ }
+ }
+
+
+ // Upload initial state images
+ if (initialStatesToBeUploaded.size() > 0 && fileManager.get() != null) {
+ DebugTool.logInfo( "Uploading soft button initial state artworks");
+ fileManager.get().uploadArtworks(initialStatesToBeUploaded, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(Map<String, String> errors) {
+ if (errors != null && errors.size() > 0) {
+ Log.e(TAG, "Error uploading soft button artworks");
+ }
+ DebugTool.logInfo( "Soft button initial artworks uploaded");
+ update(cachedListener);
+ }
+ });
+ }
+
+
+ // Upload other state images
+ if (otherStatesToBeUploaded.size() > 0 && fileManager.get() != null) {
+ DebugTool.logInfo("Uploading soft button other state artworks");
+ fileManager.get().uploadArtworks(otherStatesToBeUploaded, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(Map<String, String> errors) {
+ if (errors != null && errors.size() > 0) {
+ Log.e(TAG, "Error uploading soft button artworks");
+ }
+ DebugTool.logInfo("Soft button other state artworks uploaded");
+ // In case our soft button states have changed in the meantime
+ update(cachedListener);
+ }
+ });
+ }
+
+ // This is necessary because there may be no images needed to be uploaded
+ update(cachedListener);
+ }
+
+ /**
+ * Update the SoftButtonManger by sending a new Show RPC to reflect the changes
+ * @param listener a CompletionListener
+ */
+ protected void update(CompletionListener listener) {
+ cachedListener = listener;
+
+ if (batchUpdates) {
+ return;
+ }
+
+ // Don't send if we're in HMI NONE
+ if (currentHMILevel == null || currentHMILevel == HMILevel.HMI_NONE) {
+ return;
+ }
+
+ DebugTool.logInfo("Updating soft buttons");
+
+ cachedListener = null;
+
+
+ // Check if we have update already in progress
+ if (inProgressShowRPC != null) {
+ DebugTool.logInfo("In progress update exists, queueing update");
+ // If we already have a pending update, we're going to tell the old listener that it was superseded by a new update and then return
+ if (queuedUpdateListener != null) {
+ DebugTool.logInfo("Queued update already exists, superseding previous queued update");
+ queuedUpdateListener.onComplete(false);
+ queuedUpdateListener = null;
+ }
+
+ // Note: the queued update will be started after the in-progress one finishes
+ if (listener != null) {
+ queuedUpdateListener = listener;
+ }
+ hasQueuedUpdate = true;
+ return;
+ }
+
+
+ // Send Show RPC with soft buttons representing the current state for the soft button objects
+ inProgressListener = listener;
+ inProgressShowRPC = new Show();
+ inProgressShowRPC.setMainField1(getCurrentMainField1());
+ if (softButtonObjects == null) {
+ DebugTool.logInfo("Soft button objects are null, sending an empty array");
+ inProgressShowRPC.setSoftButtons(new ArrayList<SoftButton>());
+ } else if ((currentStateHasImages() && !allCurrentStateImagesAreUploaded()) || !softButtonImagesSupported()) {
+ // The images don't yet exist on the head unit, or we cannot use images, send a text update if possible, otherwise, don't send anything yet
+ List<SoftButton> textOnlySoftButtons = createTextSoftButtonsForCurrentState();
+ if (textOnlySoftButtons != null) {
+ DebugTool.logInfo( "Soft button images unavailable, sending text buttons");
+ inProgressShowRPC.setSoftButtons(textOnlySoftButtons);
+
+ } else {
+ DebugTool.logInfo( "Soft button images unavailable, text buttons unavailable");
+ inProgressShowRPC = null;
+ return;
+ }
+
+ } else {
+ DebugTool.logInfo( "Sending soft buttons with images");
+ inProgressShowRPC.setSoftButtons(createSoftButtonsForCurrentState());
+ }
+
+
+ inProgressShowRPC.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ DebugTool.logInfo("Soft button update completed");
+ handleResponse(true);
+ }
+
+ @Override
+ public void onError(int correlationId, Result resultCode, String info) {
+ super.onError(correlationId, resultCode, info);
+
+ Log.e(TAG, "Soft button update error");
+ handleResponse(false);
+
+ }
+
+ private void handleResponse(boolean success){
+
+ inProgressShowRPC = null;
+ CompletionListener currentListener;
+ if (inProgressListener != null) {
+ currentListener = inProgressListener;
+ inProgressListener = null;
+ currentListener.onComplete(success);
+ }
+
+
+ if (hasQueuedUpdate) {
+ DebugTool.logInfo("Queued update exists, sending another update");
+ currentListener = queuedUpdateListener;
+ queuedUpdateListener = null;
+ hasQueuedUpdate = false;
+ update(currentListener);
+ }
+ }
+ });
+
+
+ internalInterface.sendRPCRequest(inProgressShowRPC);
+ }
+
+ private boolean softButtonImagesSupported(){
+ return (displayCapabilities == null || displayCapabilities.getGraphicSupported()) && (softButtonCapabilities == null || softButtonCapabilities.getImageSupported());
+ }
+
+ /**
+ * Check if two SoftButtonObject have the same name
+ * @param softButtonObjects
+ * @return a boolean value
+ */
+ private boolean hasTwoSoftButtonObjectsOfSameName(List<SoftButtonObject> softButtonObjects) {
+ for (int i = 0; i < softButtonObjects.size(); i++) {
+ String buttonName = softButtonObjects.get(i).getName();
+ for (int j = (i + 1); j < softButtonObjects.size(); j++) {
+ if (softButtonObjects.get(j).getName().equals(buttonName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the TextField1
+ * @return currentMainField1
+ */
+ protected String getCurrentMainField1() {
+ if (currentMainField1 == null){
+ return "";
+ }
+ return currentMainField1;
+ }
+
+ /**
+ * Set the TextField1
+ * @param currentMainField1
+ */
+ protected void setCurrentMainField1(String currentMainField1) {
+ this.currentMainField1 = currentMainField1;
+ }
+
+ /**
+ * Set the batchUpdates flag that represents whether the manager should wait until commit() is called to send the updated show RPC
+ * @param batchUpdates
+ */
+ protected void setBatchUpdates(boolean batchUpdates) {
+ this.batchUpdates = batchUpdates;
+ }
+
+ /**
+ * Clean up everything after the manager is no longer needed
+ */
+ @Override
+ public void dispose() {
+ super.dispose();
+
+ // Remove listeners
+ internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, onHMIStatusListener);
+ internalInterface.removeOnRPCNotificationListener(FunctionID.ON_BUTTON_PRESS, onButtonPressListener);
+ internalInterface.removeOnRPCNotificationListener(FunctionID.ON_BUTTON_EVENT, onButtonEventListener);
+ internalInterface.removeOnSystemCapabilityListener(SystemCapabilityType.SOFTBUTTON, onSoftButtonCapabilitiesListener);
+ internalInterface.removeOnSystemCapabilityListener(SystemCapabilityType.DISPLAY, onDisplayCapabilitiesListener);
+ }
+
+ /**
+ * Check if the current state for any SoftButtonObject has images
+ * @return a boolean value
+ */
+ private boolean currentStateHasImages() {
+ for (SoftButtonObject softButtonObject : this.softButtonObjects) {
+ if (softButtonObject.getCurrentState() != null && softButtonObject.getCurrentState().getArtwork() != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if the current state for any SoftButtonObject has images that are not uploaded yet
+ * @return a boolean value
+ */
+ private boolean allCurrentStateImagesAreUploaded() {
+ if (fileManager.get() != null) {
+ for (SoftButtonObject softButtonObject : softButtonObjects) {
+ SoftButtonState currentState = softButtonObject.getCurrentState();
+ if (currentState != null && currentState.getArtwork() != null && !fileManager.get().hasUploadedFile(currentState.getArtwork())) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns text soft buttons representing the initial states of the button objects, or null if _any_ of the buttons' current states are image only buttons.
+ * @return The text soft buttons
+ */
+ private List<SoftButton> createTextSoftButtonsForCurrentState() {
+ List<SoftButton> textButtons = new ArrayList<>();
+ for (SoftButtonObject softButtonObject : softButtonObjects) {
+ SoftButton softButton = softButtonObject.getCurrentStateSoftButton();
+ if (softButton.getText() == null) {
+ return null;
+ }
+ // We should create a new softButtonObject rather than modifying the original one
+ SoftButton textOnlySoftButton = new SoftButton(SoftButtonType.SBT_TEXT, softButton.getSoftButtonID());
+ textOnlySoftButton.setText(softButton.getText());
+ textButtons.add(textOnlySoftButton);
+ }
+ return textButtons;
+ }
+
+ /**
+ * Returns a list of the SoftButton for the SoftButtonObjects' current state
+ * @return a List<SoftButton>
+ */
+ protected List<SoftButton> createSoftButtonsForCurrentState() {
+ List<SoftButton> softButtons = new ArrayList<>();
+ for (SoftButtonObject softButtonObject : softButtonObjects) {
+ softButtons.add(softButtonObject.getCurrentStateSoftButton());
+ }
+ return softButtons;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java b/base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java
new file mode 100644
index 000000000..a99875d54
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/BaseTextAndGraphicManager.java
@@ -0,0 +1,888 @@
+package com.smartdevicelink.managers.screen;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.managers.CompletionListener;
+import com.smartdevicelink.managers.file.FileManager;
+import com.smartdevicelink.managers.file.MultipleFileCompletionListener;
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.RPCNotification;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener;
+import com.smartdevicelink.proxy.rpc.DisplayCapabilities;
+import com.smartdevicelink.proxy.rpc.Image;
+import com.smartdevicelink.proxy.rpc.MetadataTags;
+import com.smartdevicelink.proxy.rpc.OnHMIStatus;
+import com.smartdevicelink.proxy.rpc.Show;
+import com.smartdevicelink.proxy.rpc.TextField;
+import com.smartdevicelink.proxy.rpc.enums.*;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.DebugTool;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static com.smartdevicelink.proxy.rpc.enums.TextAlignment.CENTERED;
+
+/**
+ * <strong>TextAndGraphicManager</strong> <br>
+ *
+ * Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br>
+ *
+ */
+abstract class BaseTextAndGraphicManager extends BaseSubManager {
+
+ private static final String TAG = "TextAndGraphicManager";
+
+ boolean isDirty, hasQueuedUpdate;
+ volatile Show inProgressUpdate;
+ Show currentScreenData, queuedImageUpdate;
+ HMILevel currentHMILevel;
+ protected DisplayCapabilities displayCapabilities;
+ private boolean pendingHMIFull, batchingUpdates;
+ private final WeakReference<FileManager> fileManager;
+ private final WeakReference<SoftButtonManager> softButtonManager;
+ private CompletionListener queuedUpdateListener, inProgressListener, pendingHMIListener;
+ SdlArtwork blankArtwork;
+ private OnRPCNotificationListener hmiListener;
+ private OnSystemCapabilityListener onDisplayCapabilitiesListener;
+ private SdlArtwork primaryGraphic, secondaryGraphic;
+ private TextAlignment textAlignment;
+ private String textField1, textField2, textField3, textField4, mediaTrackTextField;
+ private MetadataType textField1Type, textField2Type, textField3Type, textField4Type;
+
+ //Constructors
+
+ BaseTextAndGraphicManager(@NonNull ISdl internalInterface, @NonNull FileManager fileManager, @NonNull SoftButtonManager softButtonManager) {
+ // set class vars
+ super(internalInterface);
+ this.fileManager = new WeakReference<>(fileManager);
+ this.softButtonManager = new WeakReference<>(softButtonManager);
+ batchingUpdates = false;
+ isDirty = false;
+ pendingHMIFull = false;
+ textAlignment = CENTERED;
+ currentHMILevel = HMILevel.HMI_NONE;
+ currentScreenData = new Show();
+ addListeners();
+ getBlankArtwork();
+ }
+
+ @Override
+ public void start(CompletionListener listener) {
+ transitionToState(READY);
+ super.start(listener);
+ }
+
+ @Override
+ public void dispose(){
+
+ textField1 = null;
+ textField1Type = null;
+ textField2 = null;
+ textField2Type = null;
+ textField3 = null;
+ textField3Type = null;
+ textField4 = null;
+ textField4Type = null;
+ mediaTrackTextField = null;
+ textAlignment = null;
+ primaryGraphic = null;
+ secondaryGraphic = null;
+ blankArtwork = null;
+ displayCapabilities = null;
+ inProgressUpdate = null;
+ queuedImageUpdate = null;
+ currentScreenData = null;
+ queuedUpdateListener = null;
+ pendingHMIListener = null;
+ inProgressListener = null;
+ hasQueuedUpdate = false;
+ isDirty = false;
+ pendingHMIFull = false;
+
+ // remove listeners
+ internalInterface.removeOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
+ internalInterface.removeOnSystemCapabilityListener(SystemCapabilityType.DISPLAY, onDisplayCapabilitiesListener);
+
+ super.dispose();
+ }
+
+ private void addListeners() {
+ // add listener
+ hmiListener = new OnRPCNotificationListener() {
+ @Override
+ public void onNotified(RPCNotification notification) {
+ currentHMILevel = ((OnHMIStatus)notification).getHmiLevel();
+ if (currentHMILevel == HMILevel.HMI_FULL){
+ if (pendingHMIFull){
+ DebugTool.logInfo( "Acquired HMI_FULL with pending update. Sending now");
+ pendingHMIFull = false;
+ sdlUpdate(pendingHMIListener);
+ pendingHMIListener = null;
+ }
+ }
+ }
+ };
+ internalInterface.addOnRPCNotificationListener(FunctionID.ON_HMI_STATUS, hmiListener);
+
+ // Add OnDisplayCapabilitiesListener to keep displayCapabilities updated
+ onDisplayCapabilitiesListener = new OnSystemCapabilityListener() {
+ @Override
+ public void onCapabilityRetrieved(Object capability) {
+ displayCapabilities = (DisplayCapabilities)capability;
+ }
+
+ @Override
+ public void onError(String info) {
+ Log.e(TAG, "DISPLAY Capability cannot be retrieved:");
+ displayCapabilities = null;
+ }
+ };
+ this.internalInterface.addOnSystemCapabilityListener(SystemCapabilityType.DISPLAY, onDisplayCapabilitiesListener);
+ }
+
+ // Upload / Send
+
+ protected void update(CompletionListener listener) {
+
+ // check if is batch update
+ if (batchingUpdates) {
+ return;
+ }
+
+ if (isDirty){
+ isDirty = false;
+ sdlUpdate(listener);
+ } else if (listener != null) {
+ listener.onComplete(true);
+ }
+ }
+
+ private synchronized void sdlUpdate(CompletionListener listener){
+
+ // make sure hmi is not none
+ if (currentHMILevel == null || currentHMILevel == HMILevel.HMI_NONE){
+ //Trying to send show on HMI_NONE, waiting for full
+ pendingHMIFull = true;
+ if (listener != null){
+ pendingHMIListener = listener;
+ }
+ return;
+ }
+
+ //Updating Text and Graphics
+ if (inProgressUpdate != null){
+
+ //In progress update exists, queueing update
+ if (queuedUpdateListener != null){
+
+ //Queued update already exists, superseding previous queued update
+ queuedUpdateListener.onComplete(false);
+ queuedUpdateListener = null;
+ }
+
+ if (listener != null){
+ queuedUpdateListener = listener;
+ }else{
+ hasQueuedUpdate = true;
+ }
+ return;
+ }
+
+ Show fullShow = new Show();
+ fullShow.setAlignment(textAlignment);
+ fullShow = assembleShowText(fullShow);
+ fullShow = assembleShowImages(fullShow);
+
+ inProgressListener = listener;
+
+ if (!shouldUpdatePrimaryImage() && !shouldUpdateSecondaryImage()){
+
+ //No Images to send, only sending text
+ inProgressUpdate = extractTextFromShow(fullShow);
+ sendShow();
+
+ }else if (isArtworkUploadedOrDoesntExist(primaryGraphic) && ( secondaryGraphic == blankArtwork || isArtworkUploadedOrDoesntExist(secondaryGraphic))){
+
+ //Images already uploaded, sending full update
+ // The files to be updated are already uploaded, send the full show immediately
+ inProgressUpdate = fullShow;
+ sendShow();
+ } else{
+
+ // Images need to be uploaded, sending text and uploading images
+ inProgressUpdate = fullShow;
+ final Show thisUpdate = fullShow;
+
+ uploadImages(new CompletionListener() {
+ @Override
+ public void onComplete(boolean success) {
+ if (!success){
+ Log.e(TAG, "Error uploading image");
+ inProgressUpdate = extractTextFromShow(inProgressUpdate);
+ sendShow();
+ }
+ // Check if queued image update still matches our images (there could have been a new Show in the meantime)
+ // and send a new update if it does. Since the images will already be on the head unit, the whole show will be sent
+ if (thisUpdate.getGraphic() != null && thisUpdate.getGraphic().equals(queuedImageUpdate.getGraphic()) ||
+ (thisUpdate.getSecondaryGraphic() != null && queuedImageUpdate.getSecondaryGraphic() != null) && thisUpdate.getSecondaryGraphic().equals(queuedImageUpdate.getSecondaryGraphic())){
+ // Queued image update matches the images we need, sending update
+ sendShow();
+ }
+ // Else, Queued image update does not match the images we need, skipping update
+ }
+ });
+ queuedImageUpdate = fullShow;
+ }
+ }
+
+ private void sendShow(){
+ inProgressUpdate.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ handleResponse(response.getSuccess());
+ }
+
+ @Override
+ public void onError(int correlationId, Result resultCode, String info) {
+ handleResponse(false);
+ }
+
+ private void handleResponse(boolean success){
+ if (success){
+ updateCurrentScreenDataState(inProgressUpdate);
+ }
+
+ inProgressUpdate = null;
+ if (inProgressListener != null){
+ inProgressListener.onComplete(success);
+ inProgressListener = null;
+ }
+
+ if (hasQueuedUpdate){
+ //Queued update exists, sending another update
+ hasQueuedUpdate = false;
+ CompletionListener temp = queuedUpdateListener;
+ queuedUpdateListener = null;
+ sdlUpdate(temp);
+ }
+ }
+ });
+
+ if (this.softButtonManager.get() != null) {
+ this.softButtonManager.get().setCurrentMainField1(inProgressUpdate.getMainField1());
+ }
+ internalInterface.sendRPCRequest(inProgressUpdate);
+ }
+
+ // Images
+
+ private void uploadImages(final CompletionListener listener) {
+
+ List<SdlArtwork> artworksToUpload = new ArrayList<>();
+
+ // add primary image
+ if (shouldUpdatePrimaryImage()){
+ artworksToUpload.add(primaryGraphic);
+ }
+
+ // add secondary image
+ if (shouldUpdateSecondaryImage()){
+ artworksToUpload.add(secondaryGraphic);
+ }
+
+ // use file manager to upload art
+ if (fileManager.get() != null) {
+ fileManager.get().uploadArtworks(artworksToUpload, new MultipleFileCompletionListener() {
+ @Override
+ public void onComplete(Map<String, String> errors) {
+ if (errors != null) {
+ Log.e(TAG, "Error Uploading Artworks. Error: " + errors.toString());
+ listener.onComplete(false);
+ } else {
+ listener.onComplete(true);
+ }
+ }
+ });
+ }
+ }
+
+ private Show assembleShowImages(Show show){
+
+ if (shouldUpdatePrimaryImage()){
+ Image primaryImage = new Image(primaryGraphic.getName(), ImageType.DYNAMIC);
+ primaryImage.setIsTemplate(primaryGraphic.isTemplateImage());
+ show.setGraphic(primaryImage);
+ }
+
+ if (shouldUpdateSecondaryImage()){
+ Image secondaryImage = new Image(secondaryGraphic.getName(), ImageType.DYNAMIC);
+ secondaryImage.setIsTemplate(secondaryGraphic.isTemplateImage());
+ show.setSecondaryGraphic(secondaryImage);
+ }
+
+ return show;
+ }
+
+ // Text
+
+ Show assembleShowText(Show show){
+
+ show = setBlankTextFields(show);
+
+ if (mediaTrackTextField != null){
+ show.setMediaTrack(mediaTrackTextField);
+ }
+
+ List<String> nonNullFields = findValidMainTextFields();
+ if (nonNullFields.isEmpty()){
+ return show;
+ }
+
+ int numberOfLines = getNumberOfLines();
+
+ switch (numberOfLines) {
+ case 1: show = assembleOneLineShowText(show, nonNullFields);
+ break;
+ case 2: show = assembleTwoLineShowText(show);
+ break;
+ case 3: show = assembleThreeLineShowText(show);
+ break;
+ case 4: show = assembleFourLineShowText(show);
+ break;
+ }
+
+ return show;
+ }
+
+ private Show assembleOneLineShowText(Show show, List<String> showFields){
+
+ StringBuilder showString1 = new StringBuilder();
+ for (int i = 0; i < showFields.size(); i++) {
+ if (i > 0) {
+ showString1.append(" - ").append(showFields.get(i));
+ }else{
+ showString1.append(showFields.get(i));
+ }
+ }
+ show.setMainField1(showString1.toString());
+
+ MetadataTags tags = new MetadataTags();
+ tags.setMainField1(findNonNullMetadataFields());
+
+ show.setMetadataTags(tags);
+
+ return show;
+ }
+
+ private Show assembleTwoLineShowText(Show show){
+
+ StringBuilder tempString = new StringBuilder();
+ MetadataTags tags = new MetadataTags();
+
+ if (textField1 != null && textField1.length() > 0) {
+ tempString.append(textField1);
+ if (textField1Type != null){
+ tags.setMainField1(textField1Type);
+ }
+ }
+
+ if (textField2 != null && textField2.length() > 0) {
+ if (( textField3 == null || !(textField3.length() > 0)) && (textField4 == null || !(textField4.length() > 0))){
+ // text does not exist in slots 3 or 4, put text2 in slot 2
+ show.setMainField2(textField2);
+ if (textField2Type != null){
+ tags.setMainField2(textField2Type);
+ }
+ } else if (textField1 != null && textField1.length() > 0) {
+ // If text 1 exists, put it in slot 1 formatted
+ tempString.append(" - ").append(textField2);
+ if (textField2Type != null){
+ List<MetadataType> typeList = new ArrayList<>();
+ typeList.add(textField2Type);
+ if (textField1Type != null){
+ typeList.add(textField1Type);
+ }
+ tags.setMainField1(typeList);
+ }
+ }else {
+ // If text 1 does not exist, put it in slot 1 unformatted
+ tempString.append(textField2);
+ if (textField2Type != null){
+ tags.setMainField1(textField2Type);
+ }
+ }
+ }
+
+ // set mainfield 1
+ show.setMainField1(tempString.toString());
+
+ // new stringbuilder object
+ tempString = new StringBuilder();
+
+ if (textField3 != null && textField3.length() > 0){
+ // If text 3 exists, put it in slot 2
+ tempString.append(textField3);
+ if (textField3Type != null){
+ List<MetadataType> typeList = new ArrayList<>();
+ typeList.add(textField3Type);
+ tags.setMainField2(typeList);
+ }
+ }
+
+ if (textField4 != null && textField4.length() > 0){
+ if (textField3 != null && textField3.length() > 0){
+ // If text 3 exists, put it in slot 2 formatted
+ tempString.append(" - ").append(textField4);
+ if (textField4Type != null){
+ List<MetadataType> typeList = new ArrayList<>();
+ typeList.add(textField4Type);
+ if (textField3Type != null){
+ typeList.add(textField3Type);
+ }
+ tags.setMainField2(typeList);
+ }
+ } else {
+ // If text 3 does not exist, put it in slot 3 unformatted
+ tempString.append(textField4);
+ if (textField4Type != null){
+ tags.setMainField2(textField4Type);
+ }
+ }
+ }
+
+ if (tempString.toString().length() > 0){
+ show.setMainField2(tempString.toString());
+ }
+
+ show.setMetadataTags(tags);
+ return show;
+ }
+
+ private Show assembleThreeLineShowText(Show show){
+
+ MetadataTags tags = new MetadataTags();
+
+ if (textField1 != null && textField1.length() > 0) {
+ show.setMainField1(textField1);
+ if (textField1Type != null){
+ tags.setMainField1(textField1Type);
+ }
+ }
+
+ if (textField2 != null && textField2.length() > 0) {
+ show.setMainField2(textField2);
+ if (textField2Type != null){
+ tags.setMainField2(textField2Type);
+ }
+ }
+
+ StringBuilder tempString = new StringBuilder();
+
+ if (textField3 != null && textField3.length() > 0){
+ tempString.append(textField3);
+ if (textField3Type != null){
+ tags.setMainField3(textField3Type);
+ }
+ }
+
+ if (textField4 != null && textField4.length() > 0) {
+ if (textField3 != null && textField3.length() > 0) {
+ // If text 3 exists, put it in slot 3 formatted
+ tempString.append(" - ").append(textField4);
+ if (textField4Type != null){
+ List<MetadataType> tags4 = new ArrayList<>();
+ if (textField3Type != null){
+ tags4.add(textField3Type);
+ }
+ tags4.add(textField4Type);
+ tags.setMainField3(tags4);
+ }
+ } else {
+ // If text 3 does not exist, put it in slot 3 formatted
+ tempString.append(textField4);
+ if (textField4Type != null){
+ tags.setMainField3(textField4Type);
+ }
+ }
+ }
+
+ show.setMainField3(tempString.toString());
+ show.setMetadataTags(tags);
+ return show;
+ }
+
+ private Show assembleFourLineShowText(Show show){
+
+ MetadataTags tags = new MetadataTags();
+
+ if (textField1 != null && textField1.length() > 0) {
+ show.setMainField1(textField1);
+ if (textField1Type != null){
+ tags.setMainField1(textField1Type);
+ }
+ }
+
+ if (textField2 != null && textField2.length() > 0) {
+ show.setMainField2(textField2);
+ if (textField2Type != null){
+ tags.setMainField2(textField2Type);
+ }
+ }
+
+ if (textField3 != null && textField3.length() > 0) {
+ show.setMainField3(textField3);
+ if (textField3Type != null){
+ tags.setMainField3(textField3Type);
+ }
+ }
+
+ if (textField4 != null && textField4.length() > 0) {
+ show.setMainField4(textField4);
+ if (textField4Type != null){
+ tags.setMainField4(textField4Type);
+ }
+ }
+
+ show.setMetadataTags(tags);
+ return show;
+ }
+
+ // Extraction
+
+ Show extractTextFromShow(Show show){
+
+ Show newShow = new Show();
+ newShow.setMainField1(show.getMainField1());
+ newShow.setMainField2(show.getMainField2());
+ newShow.setMainField3(show.getMainField3());
+ newShow.setMainField4(show.getMainField4());
+
+ return newShow;
+ }
+
+ private Show setBlankTextFields(Show newShow){
+
+ newShow.setMainField1("");
+ newShow.setMainField2("");
+ newShow.setMainField3("");
+ newShow.setMainField4("");
+ newShow.setMediaTrack("");
+
+ return newShow;
+ }
+
+ private void updateCurrentScreenDataState(Show show){
+
+ if (show == null){
+ Log.e(TAG, "can not updateCurrentScreenDataFromShow from null show");
+ return;
+ }
+
+ // If the items are null, they were not updated, so we can't just set it directly
+ if (show.getMainField1() != null){
+ currentScreenData.setMainField1(show.getMainField1());
+ }
+ if (show.getMainField2() != null){
+ currentScreenData.setMainField2(show.getMainField2());
+ }
+ if (show.getMainField3() != null){
+ currentScreenData.setMainField3(show.getMainField3());
+ }
+ if (show.getMainField4() != null){
+ currentScreenData.setMainField4(show.getMainField4());
+ }
+ if (show.getMediaTrack() != null){
+ currentScreenData.setMediaTrack(show.getMediaTrack());
+ }
+ if (show.getMetadataTags() != null){
+ currentScreenData.setMetadataTags(show.getMetadataTags());
+ }
+ if (show.getAlignment() != null){
+ currentScreenData.setAlignment(show.getAlignment());
+ }
+ if (show.getGraphic() != null){
+ currentScreenData.setGraphic(show.getGraphic());
+ }
+ if (show.getSecondaryGraphic() != null){
+ currentScreenData.setSecondaryGraphic(show.getSecondaryGraphic());
+ }
+ }
+
+ // Helpers
+
+ private List<String> findValidMainTextFields(){
+ List<String> array = new ArrayList<>();
+
+ if (textField1 != null && textField1.length() > 0) {
+ array.add(textField1);
+ }
+
+ if (textField2 != null && textField2.length() > 0) {
+ array.add(textField2);
+ }
+
+ if (textField3 != null && textField3.length() > 0) {
+ array.add(textField3);
+ }
+
+ if (textField4 != null && textField4.length() > 0) {
+ array.add(textField4);
+ }
+
+ return array;
+ }
+
+
+ private List<MetadataType> findNonNullMetadataFields(){
+ List<MetadataType> array = new ArrayList<>();
+
+ if (textField1Type != null) {
+ array.add(textField1Type);
+ }
+
+ if (textField2Type != null) {
+ array.add(textField2Type);
+ }
+
+ if (textField3Type != null) {
+ array.add(textField3Type);
+ }
+
+ if (textField4Type != null) {
+ array.add(textField4Type);
+ }
+
+ return array;
+ }
+
+ abstract SdlArtwork getBlankArtwork();
+
+ private boolean isArtworkUploadedOrDoesntExist(SdlArtwork artwork){
+
+ if (fileManager.get() != null){
+ return artwork != null && fileManager.get().hasUploadedFile(artwork);
+ }
+
+ return false;
+ }
+
+ private boolean shouldUpdatePrimaryImage() {
+ if (displayCapabilities == null || displayCapabilities.getGraphicSupported()) {
+ if (currentScreenData.getGraphic() == null && primaryGraphic != null) {
+ return true;
+ } else if (currentScreenData.getGraphic() == null && primaryGraphic == null) {
+ return false;
+ }
+ return currentScreenData != null && (primaryGraphic != null && !currentScreenData.getGraphic().getValue().equalsIgnoreCase(primaryGraphic.getName()));
+ }
+ return false;
+ }
+
+ private boolean shouldUpdateSecondaryImage() {
+ // Cannot detect if there is a secondary image, so we'll just try to detect if there's a primary image and allow it if there is.
+ if (displayCapabilities == null || displayCapabilities.getGraphicSupported()) {
+ if (currentScreenData.getGraphic() == null && secondaryGraphic != null) {
+ return true;
+ } else if (currentScreenData.getGraphic() == null && secondaryGraphic == null) {
+ return false;
+ }
+ return currentScreenData != null && (secondaryGraphic != null && !currentScreenData.getGraphic().getValue().equalsIgnoreCase(secondaryGraphic.getName()));
+ }
+ return false;
+ }
+
+ int getNumberOfLines() {
+
+ if (displayCapabilities == null){
+ return 4;
+ }
+
+ int linesFound = 0;
+
+ List<TextField> textFields = displayCapabilities.getTextFields();
+ TextFieldName name;
+ for (TextField field : textFields) {
+ if (field.getName() != null) {
+ name = field.getName();
+ if (name == TextFieldName.mainField1 || name == TextFieldName.mainField2 || name == TextFieldName.mainField3 || name == TextFieldName.mainField4) {
+ linesFound += 1;
+ }
+ }
+ }
+
+ return linesFound;
+ }
+
+ // SCREEN ITEM SETTERS AND GETTERS
+
+ void setTextAlignment(TextAlignment textAlignment){
+ this.textAlignment = textAlignment;
+ // If we aren't batching, send the update immediately, if we are, set ourselves as dirty (so we know we should send an update after the batch ends)
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ TextAlignment getTextAlignment(){
+ return textAlignment;
+ }
+
+ void setMediaTrackTextField(String mediaTrackTextField){
+ this.mediaTrackTextField = mediaTrackTextField;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ String getMediaTrackTextField(){
+ return mediaTrackTextField;
+ }
+
+ void setTextField1(String textField1){
+ this.textField1 = textField1;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ String getTextField1(){
+ return textField1;
+ }
+
+ void setTextField2(String textField2){
+ this.textField2 = textField2;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ String getTextField2(){
+ return textField2;
+ }
+
+ void setTextField3(String textField3){
+ this.textField3 = textField3;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ String getTextField3(){
+ return textField3;
+ }
+
+ void setTextField4(String textField4){
+ this.textField4 = textField4;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ String getTextField4(){
+ return textField4;
+ }
+
+ void setTextField1Type(MetadataType textField1Type){
+ this.textField1Type = textField1Type;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ MetadataType getTextField1Type(){
+ return textField1Type;
+ }
+
+ void setTextField2Type(MetadataType textField2Type){
+ this.textField2Type = textField2Type;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ MetadataType getTextField2Type(){
+ return textField2Type;
+ }
+
+ void setTextField3Type(MetadataType textField3Type){
+ this.textField3Type = textField3Type;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ MetadataType getTextField3Type(){
+ return textField3Type;
+ }
+
+ void setTextField4Type(MetadataType textField4Type){
+ this.textField4Type = textField4Type;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ MetadataType getTextField4Type(){
+ return textField4Type;
+ }
+
+ void setPrimaryGraphic(SdlArtwork primaryGraphic){
+ this.primaryGraphic = primaryGraphic;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ SdlArtwork getPrimaryGraphic(){
+ return primaryGraphic;
+ }
+
+ void setSecondaryGraphic(SdlArtwork secondaryGraphic){
+ this.secondaryGraphic = secondaryGraphic;
+ if (!batchingUpdates){
+ sdlUpdate(null);
+ }else{
+ isDirty = true;
+ }
+ }
+
+ SdlArtwork getSecondaryGraphic(){
+ return secondaryGraphic;
+ }
+
+ void setBatchUpdates(boolean batching){
+ this.batchingUpdates = batching;
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java
new file mode 100644
index 000000000..01c760d09
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonObject.java
@@ -0,0 +1,272 @@
+package com.smartdevicelink.managers.screen;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.smartdevicelink.proxy.rpc.OnButtonEvent;
+import com.smartdevicelink.proxy.rpc.OnButtonPress;
+import com.smartdevicelink.proxy.rpc.SoftButton;
+import com.smartdevicelink.util.DebugTool;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * <strong>SoftButtonObject</strong> <br>
+ * SoftButtonObject define a button that can have multiple SoftButtonState values.<br>
+ * The states of SoftButtonObject allow the developer to not have to manage multiple SoftButtons that have very similar functionality.<br>
+ * For example, a repeat button in a music app can be thought of as one SoftButtonObject with three typical states: repeat off, repeat 1, and repeat on.<br>
+ * @see SoftButtonState
+ */
+public class SoftButtonObject {
+
+ private static final String TAG = "SoftButtonObject";
+ private String name;
+ private List<SoftButtonState> states;
+ private String currentStateName;
+ private int buttonId;
+ private OnEventListener onEventListener;
+ private UpdateListener updateListener;
+
+ /**
+ * Create a new instance of the SoftButtonObject with multiple states
+ * @param name a String value represents name of the object
+ * @param states a list of SoftButtonState represents the SoftButtonState values for the object
+ * @param initialStateName a String value represents the name for the initial state
+ * @param onEventListener a listener that has a callback that will be triggered when a button event happens
+ * Note: the initialStateName should match exactly the name of one of the states for the object. Otherwise an exception will be thrown.
+ */
+ public SoftButtonObject(@NonNull String name, @NonNull List<SoftButtonState> states, @NonNull String initialStateName, OnEventListener onEventListener) {
+
+ // Make sure there aren't two states with the same name
+ if (hasTwoStatesOfSameName(states)) {
+ Log.e(TAG, "Two states have the same name in states list for soft button object");
+ return;
+ }
+
+ this.name = name;
+ this.states = states;
+ currentStateName = initialStateName;
+ this.buttonId = 0;
+ this.onEventListener = onEventListener;
+ }
+
+ /**
+ * Create a new instance of the SoftButtonObject with one state
+ * @param name a String value represents name of the object
+ * @param state a SoftButtonState represents state for the object
+ * @param onEventListener a listener that has a callback that will be triggered when a button event happens
+ */
+ public SoftButtonObject(@NonNull String name, @NonNull SoftButtonState state, OnEventListener onEventListener) {
+ this(name, Collections.singletonList(state), state.getName(), onEventListener);
+ }
+
+ /**
+ * Transition the SoftButtonObject to a specific state
+ * @param newStateName a String value represents the name fo the state that we want to transition the SoftButtonObject to
+ * @return a boolean value that represents whether the transition succeeded or failed
+ */
+ public boolean transitionToStateByName(@NonNull String newStateName) {
+ SoftButtonState newState = getStateByName(newStateName);
+ if (newState == null) {
+ Log.e(TAG, String.format("Attempted to transition to state: %s on soft button object: %s but no state with that name was found", newStateName, this.name));
+ return false;
+ }
+ DebugTool.logInfo(String.format("Transitioning soft button object %s to state %s", this.name, newStateName));
+ currentStateName = newStateName;
+
+ // Send a new Show RPC because the state has changed which means the actual SoftButton has changed
+ if (updateListener != null) {
+ updateListener.onUpdate();
+ } else {
+ Log.e(TAG, String.format("SoftButtonManager is not set for soft button object: %s. Update cannot be triggered", this.name));
+ }
+
+ return true;
+ }
+
+ /**
+ * Transition the SoftButtonObject to the next state
+ */
+ public void transitionToNextState() {
+ String nextStateName = null;
+ for (int i = 0; i < states.size(); i++) {
+ if (states.get(i).getName().equals(currentStateName)) {
+ if (i == (states.size() - 1)) {
+ nextStateName = states.get(0).getName();
+ } else {
+ nextStateName = states.get(i + 1).getName();
+ }
+ break;
+ }
+ }
+ if (nextStateName == null) {
+ Log.e(TAG, String.format("Current state name : %s cannot be found for soft button object %s", currentStateName, this.name));
+ return;
+ }
+ transitionToStateByName(nextStateName);
+ }
+
+ /**
+ * Get the current state for the SoftButtonObject
+ * @return a SoftButtonState represents the current state
+ */
+ public SoftButtonState getCurrentState() {
+ SoftButtonState state = getStateByName(currentStateName);
+ if (state == null) {
+ Log.e(TAG, String.format("Current state name : %s cannot be found for soft button object %s", currentStateName, this.name));
+ }
+ return state;
+ }
+
+ /**
+ * Get the SoftButton object for the current state
+ * @return a SoftButton object that is associated with the current state
+ */
+ public SoftButton getCurrentStateSoftButton() {
+ SoftButtonState currentState = getCurrentState();
+ if (currentState == null || currentState.getSoftButton() == null) {
+ return null;
+ }
+
+ SoftButton softButton = currentState.getSoftButton();
+ softButton.setSoftButtonID(this.buttonId);
+ return softButton;
+ }
+
+ /**
+ * Find and get the SoftButtonState that has the provided name
+ * @param stateName a String value that represents the name of the state
+ * @return a SoftButtonState object that represents the state that has the provided name
+ */
+ private SoftButtonState getStateByName(String stateName) {
+ if (stateName != null && states != null) {
+ for (SoftButtonState state : states) {
+ if (state.getName().equals(stateName)) {
+ return state;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Check if two SoftButtonState have the same name
+ * @param states a list of SoftButtonState
+ * @return a boolean value that represents whether we have two states with the same name
+ */
+ private boolean hasTwoStatesOfSameName(List<SoftButtonState> states) {
+ for (int i = 0; i < states.size(); i++) {
+ String stateName = states.get(i).getName();
+ for (int j = (i + 1); j < states.size(); j++) {
+ if (states.get(j).getName().equals(stateName)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Set the SoftButtonManager's update listener
+ * @param updateListener the SoftButtonManager.UpdateListener object
+ */
+ protected void setUpdateListener(UpdateListener updateListener) {
+ this.updateListener = updateListener;
+ }
+
+ /**
+ * Get the name of the SoftButtonObject
+ * @return a String that represents the name of the SoftButtonObject
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the name of the SoftButtonObject
+ * @param name a String that represents the name of the SoftButtonObject
+ */
+ public void setName(@NonNull String name) {
+ this.name = name;
+ }
+
+ /**
+ * Get the SoftButtonState list
+ * @return a list of the object's soft button states
+ */
+ public List<SoftButtonState> getStates() {
+ return states;
+ }
+
+ /**
+ * Set the the SoftButtonState list
+ * @param states a list of the object's soft button states
+ */
+ public void setStates(@NonNull List<SoftButtonState> states) {
+ this.states = states;
+ }
+
+ /**
+ * Get the name of the current state
+ * @return a String that represents the name of the current state
+ */
+ public String getCurrentStateName() {
+ return currentStateName;
+ }
+
+ /**
+ * Set the name of the current state
+ * @param currentStateName a String that represents the name of the current state
+ */
+ public void setCurrentStateName(@NonNull String currentStateName) {
+ this.currentStateName = currentStateName;
+ }
+
+ /**
+ * Get the dd of the SoftButtonObject
+ * @return an int value that represents the id of the SoftButtonObject
+ */
+ public int getButtonId() {
+ return buttonId;
+ }
+
+ /**
+ * Set the id of the SoftButtonObject
+ * @param buttonId an int value that represents the id of the SoftButtonObject
+ */
+ public void setButtonId(int buttonId) {
+ this.buttonId = buttonId;
+ }
+
+ /**
+ * Get the event listener for the SoftButtonObject
+ * @return OnEventListener
+ */
+ public OnEventListener getOnEventListener() {
+ return onEventListener;
+ }
+
+ /**
+ * Set the event listener for the SoftButtonObject
+ * @param onEventListener a listener that has a callback that will be triggered when a button event happens
+ */
+ public void setOnEventListener(OnEventListener onEventListener) {
+ this.onEventListener = onEventListener;
+ }
+
+ public interface OnEventListener{
+ void onPress(SoftButtonObject softButtonObject, OnButtonPress onButtonPress);
+ void onEvent(SoftButtonObject softButtonObject, OnButtonEvent onButtonEvent);
+ }
+
+ /**
+ * A listener interface that is used by SoftButtonObject to request an update from SoftButtonManager
+ */
+ interface UpdateListener{
+ /**
+ * Requests an update from SoftButtonManager
+ */
+ void onUpdate();
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonState.java b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonState.java
new file mode 100644
index 000000000..67458ce2c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/screen/SoftButtonState.java
@@ -0,0 +1,97 @@
+package com.smartdevicelink.managers.screen;
+
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import com.smartdevicelink.managers.file.filetypes.SdlArtwork;
+import com.smartdevicelink.proxy.rpc.Image;
+import com.smartdevicelink.proxy.rpc.SoftButton;
+import com.smartdevicelink.proxy.rpc.enums.ImageType;
+import com.smartdevicelink.proxy.rpc.enums.SoftButtonType;
+
+/**
+ * <strong>SoftButtonState</strong> <br>
+ * Defines an individual state for SoftButtonObject.<br>
+ * The states of SoftButtonObject allow the developer to not have to manage multiple SoftButtons that have very similar functionality.<br>
+ * For example, a repeat button in a music app can be thought of as one SoftButtonObject with three typical states: repeat off, repeat 1, and repeat on.<br>
+ * @see SoftButtonObject
+ */
+public class SoftButtonState {
+
+ private static final String TAG = "SoftButtonState";
+ private String name;
+ private SdlArtwork artwork;
+ private final SoftButton softButton;
+
+ /**
+ * Creates a new instance of SoftButtonState
+ * Note: state names should be different for each SoftButtonObject
+ * @param name a String value represents name of the state
+ * @param text a String represents the text for the state
+ * @param artwork an SdlArtwork represents the artwork for the state
+ */
+ public SoftButtonState(@NonNull String name, String text, SdlArtwork artwork) {
+ if (text == null && artwork == null) {
+ Log.e(TAG, "Attempted to create an invalid soft button state: text and artwork are both null");
+ softButton = null;
+ return;
+ }
+ this.name = name;
+ this.artwork = artwork;
+
+
+ // Create a SoftButton and set its Type
+ SoftButtonType type;
+ if (artwork != null && text != null) {
+ type = SoftButtonType.SBT_BOTH;
+ } else if (artwork != null) {
+ type = SoftButtonType.SBT_IMAGE;
+ } else {
+ type = SoftButtonType.SBT_TEXT;
+ }
+ this.softButton = new SoftButton(type, 0);
+
+
+ // Set the SoftButton's image
+ if (artwork != null) {
+ softButton.setImage(new Image(artwork.getName(), ImageType.DYNAMIC));
+ }
+
+ // Set the SoftButton's text
+ if (text != null) {
+ softButton.setText(text);
+ }
+ }
+
+ /**
+ * Get the state name
+ * @return a String value represents the name of the state
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Set the state name
+ * @param name a String value represents the name of the state
+ */
+ public void setName(@NonNull String name) {
+ this.name = name;
+ }
+
+ /**
+ * Get the SoftButton for the state
+ * @return a SoftButton object represents the SoftButton for the state
+ */
+ public SoftButton getSoftButton() {
+ return softButton;
+ }
+
+ /**
+ * Get the Artwork for the state
+ * @return an SdlArtwork object represents the artwork for the state
+ */
+ public SdlArtwork getArtwork() {
+ return artwork;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/managers/video/BaseVideoStreamManager.java b/base/src/main/java/com/smartdevicelink/managers/video/BaseVideoStreamManager.java
new file mode 100644
index 000000000..ebcd74f76
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/managers/video/BaseVideoStreamManager.java
@@ -0,0 +1,11 @@
+package com.smartdevicelink.managers.video;
+
+import android.support.annotation.NonNull;
+import com.smartdevicelink.managers.BaseSubManager;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+
+abstract class BaseVideoStreamManager extends BaseSubManager {
+ public BaseVideoStreamManager(@NonNull ISdl internalInterface) {
+ super(internalInterface);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/marshal/JsonRPCMarshaller.java b/base/src/main/java/com/smartdevicelink/marshal/JsonRPCMarshaller.java
new file mode 100644
index 000000000..493c21cee
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/marshal/JsonRPCMarshaller.java
@@ -0,0 +1,127 @@
+package com.smartdevicelink.marshal;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.smartdevicelink.proxy.RPCMessage;
+import com.smartdevicelink.proxy.RPCStruct;
+import com.smartdevicelink.trace.*;
+import com.smartdevicelink.trace.enums.InterfaceActivityDirection;
+import com.smartdevicelink.util.DebugTool;
+
+/*
+ * Responsible for marshalling and unmarshing between RPC Objects and byte streams that are sent
+ * over transmission
+ */
+
+public class JsonRPCMarshaller {
+
+ private static final String SDL_LIB_PRIVATE_KEY = "42baba60-eb57-11df-98cf-0800200c9a66";
+
+ /**
+ * @param msg RPC message to be marshaled
+ * @param version protocol version
+ * @return byte array of the marshalled message
+ */
+ public static byte[] marshall(RPCMessage msg, byte version) {
+ byte[] jsonBytes = null;
+ try {
+ JSONObject jsonObject = msg.serializeJSON(version);
+ jsonBytes = jsonObject.toString().getBytes();
+
+ SdlTrace.logMarshallingEvent(InterfaceActivityDirection.Transmit, jsonBytes, SDL_LIB_PRIVATE_KEY);
+ } catch (JSONException e) {
+ DebugTool.logError("Failed to encode messages to JSON.", e);
+ }
+ return jsonBytes;
+ }
+
+ public static Hashtable<String, Object> unmarshall(byte[] message) {
+ SdlTrace.logMarshallingEvent(InterfaceActivityDirection.Receive, message, SDL_LIB_PRIVATE_KEY);
+ Hashtable<String, Object> ret = null;
+ try {
+ String jsonString = new String(message);
+ JSONObject jsonObject = new JSONObject(jsonString);
+ ret = deserializeJSONObject(jsonObject);
+ } catch (JSONException e) {
+ DebugTool.logError("Failed to parse JSON", e);
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Hashtable<String, Object> deserializeJSONObject(JSONObject jsonObject)
+ throws JSONException {
+ Hashtable<String, Object> ret = new Hashtable<String, Object>();
+ Iterator<String> it = jsonObject.keys();
+ String key = null;
+ while (it.hasNext()) {
+ key = it.next();
+ Object value = jsonObject.get(key);
+ if (value instanceof JSONObject) {
+ ret.put(key, deserializeJSONObject((JSONObject)value));
+ } else if (value instanceof JSONArray) {
+ JSONArray arrayValue = (JSONArray) value;
+ List<Object> putList = new ArrayList<Object>(arrayValue.length());
+ for (int i = 0; i < arrayValue.length(); i++) {
+ Object anObject = arrayValue.get(i);
+ if (anObject instanceof JSONObject) {
+ Hashtable<String, Object> deserializedObject = deserializeJSONObject((JSONObject)anObject);
+ putList.add(deserializedObject);
+ } else {
+ putList.add(anObject);
+ }
+ }
+ ret.put(key, putList);
+ } else {
+ ret.put(key, value);
+ }
+ }
+ return ret;
+ }
+
+ @SuppressWarnings("unchecked" )
+ private static JSONArray serializeList(List<?> list) throws JSONException{
+ JSONArray toPut = new JSONArray();
+ Iterator<Object> valueIterator = (Iterator<Object>) list.iterator();
+ while(valueIterator.hasNext()){
+ Object anObject = valueIterator.next();
+ if (anObject instanceof RPCStruct) {
+ RPCStruct toSerialize = (RPCStruct) anObject;
+ toPut.put(toSerialize.serializeJSON());
+ } else if(anObject instanceof Hashtable){
+ Hashtable<String, Object> toSerialize = (Hashtable<String, Object>)anObject;
+ toPut.put(serializeHashtable(toSerialize));
+ } else {
+ toPut.put(anObject);
+ }
+ }
+ return toPut;
+ }
+
+ @SuppressWarnings({"unchecked" })
+ public static JSONObject serializeHashtable(Hashtable<String, Object> hash) throws JSONException{
+ JSONObject obj = new JSONObject();
+ Iterator<String> hashKeyIterator = hash.keySet().iterator();
+ while (hashKeyIterator.hasNext()){
+ String key = (String) hashKeyIterator.next();
+ Object value = hash.get(key);
+ if (value instanceof RPCStruct) {
+ obj.put(key, ((RPCStruct) value).serializeJSON());
+ } else if (value instanceof List<?>) {
+ obj.put(key, serializeList((List<?>) value));
+ } else if (value instanceof Hashtable) {
+ obj.put(key, serializeHashtable((Hashtable<String, Object>)value));
+ } else {
+ obj.put(key, value);
+ }
+ }
+ return obj;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/BinaryFrameHeader.java b/base/src/main/java/com/smartdevicelink/protocol/BinaryFrameHeader.java
new file mode 100644
index 000000000..f15dea3d0
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/BinaryFrameHeader.java
@@ -0,0 +1,118 @@
+package com.smartdevicelink.protocol;
+
+import android.util.Log;
+
+import com.smartdevicelink.util.BitConverter;
+
+public class BinaryFrameHeader {
+ private static final String TAG = "BinaryFrameHeader";
+
+ private byte _rpcType;
+ private int _functionID;
+ private int _correlationID;
+ private int _jsonSize;
+
+ private byte[] _jsonData;
+ private byte[] _bulkData;
+
+ public BinaryFrameHeader() {}
+
+ public static BinaryFrameHeader parseBinaryHeader(byte[] binHeader) {
+ BinaryFrameHeader msg = new BinaryFrameHeader();
+
+ byte RPC_Type = (byte) (binHeader[0] >>> 4);
+ msg.setRPCType(RPC_Type);
+
+ int _functionID = (BitConverter.intFromByteArray(binHeader, 0) & 0x0FFFFFFF);
+ msg.setFunctionID(_functionID);
+
+ int corrID = BitConverter.intFromByteArray(binHeader, 4);
+ msg.setCorrID(corrID);
+
+ int _jsonSize = BitConverter.intFromByteArray(binHeader, 8);
+ msg.setJsonSize(_jsonSize);
+
+ try {
+ if (_jsonSize > 0) {
+ byte[] _jsonData = new byte[_jsonSize];
+ System.arraycopy(binHeader, 12, _jsonData, 0, _jsonSize);
+ msg.setJsonData(_jsonData);
+ }
+
+ if (binHeader.length - _jsonSize - 12 > 0) {
+ byte[] _bulkData = new byte[binHeader.length - _jsonSize - 12];
+ System.arraycopy(binHeader, 12 + _jsonSize, _bulkData, 0, _bulkData.length);
+ msg.setBulkData(_bulkData);
+ }
+ } catch (OutOfMemoryError|ArrayIndexOutOfBoundsException e){
+ Log.e(TAG, "Unable to process data to form header");
+ return null;
+ }
+
+ return msg;
+ }
+
+ public byte[] assembleHeaderBytes() {
+ int binHeader = _functionID;
+ // reset the 4 leftmost bits, for _rpcType
+ binHeader &= 0xFFFFFFFF >>> 4;
+ binHeader |= (_rpcType << 28);
+
+ byte[] ret = new byte[12];
+ System.arraycopy(BitConverter.intToByteArray(binHeader), 0, ret, 0, 4);
+ System.arraycopy(BitConverter.intToByteArray(_correlationID), 0, ret, 4, 4);
+ System.arraycopy(BitConverter.intToByteArray(_jsonSize), 0, ret, 8, 4);
+
+ return ret;
+ }
+
+ public byte getRPCType() {
+ return _rpcType;
+ }
+
+ public void setRPCType(byte _rpcType) {
+ this._rpcType = _rpcType;
+ }
+
+ public int getFunctionID() {
+ return _functionID;
+ }
+
+ public void setFunctionID(int _functionID) {
+ this._functionID = _functionID;
+ }
+
+ public int getCorrID() {
+ return _correlationID;
+ }
+
+ public void setCorrID(int _correlationID) {
+ this._correlationID = _correlationID;
+ }
+
+ public int getJsonSize() {
+ return _jsonSize;
+ }
+
+ public void setJsonSize(int _jsonSize) {
+ this._jsonSize = _jsonSize;
+ }
+
+ public byte[] getJsonData() {
+ return _jsonData;
+ }
+
+ public void setJsonData(byte[] _jsonData) {
+ this._jsonData = new byte[this._jsonSize];
+ System.arraycopy(_jsonData, 0, this._jsonData, 0, _jsonSize);
+ //this._jsonData = _jsonData;
+ }
+
+ public byte[] getBulkData() {
+ return _bulkData;
+ }
+
+ public void setBulkData(byte[] _bulkData) {
+ this._bulkData = _bulkData;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/IProtocolListener.java b/base/src/main/java/com/smartdevicelink/protocol/IProtocolListener.java
new file mode 100644
index 000000000..6a4065707
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/IProtocolListener.java
@@ -0,0 +1,44 @@
+package com.smartdevicelink.protocol;
+
+
+import com.smartdevicelink.protocol.enums.*;
+
+import java.util.List;
+
+public interface IProtocolListener {
+ // Called to indicate that these bytes are to be sent as part of a message.
+ // This call includes the part of the message.
+ void onProtocolMessageBytesToSend(SdlPacket packet);
+
+ // Called to indicate that a complete message (RPC, BULK, etc.) has been
+ // received. This call includes the message.
+ void onProtocolMessageReceived(ProtocolMessage msg);
+
+ // Called to indicate that a protocol session has been started (from either side)
+ void onProtocolSessionStarted(SessionType sessionType, byte sessionID, byte version, String correlationID, int hashID, boolean isEncrypted);
+
+ void onProtocolSessionNACKed(SessionType sessionType, byte sessionID, byte version,
+ String correlationID, List<String> rejectedParams);
+
+ // Called to indicate that a protocol session has ended (from either side)
+ void onProtocolSessionEnded(SessionType sessionType, byte sessionID, String correlationID /*, String info, Exception ex*/);
+
+ void onProtocolSessionEndedNACKed(SessionType sessionType, byte sessionID, String correlationID /*, String info, Exception ex*/);
+
+ void onProtocolHeartbeat(SessionType sessionType, byte sessionID);
+
+ /**
+ * Called when a protocol heartbeat ACK message has been received from SDL.
+ */
+ void onProtocolHeartbeatACK(SessionType sessionType, byte sessionID);
+
+ void onProtocolServiceDataACK(SessionType sessionType, int dataSize, byte sessionID);
+
+ void onResetOutgoingHeartbeat(SessionType sessionType, byte sessionID);
+
+ void onResetIncomingHeartbeat(SessionType sessionType, byte sessionID);
+
+ // Called to indicate that a protocol error was detected in received data.
+ void onProtocolError(String info, Exception e);
+
+} // end-interfCe
diff --git a/base/src/main/java/com/smartdevicelink/protocol/ISdlProtocol.java b/base/src/main/java/com/smartdevicelink/protocol/ISdlProtocol.java
new file mode 100644
index 000000000..2e9dcb492
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/ISdlProtocol.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.protocol;
+
+
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.security.SdlSecurityBase;
+import com.smartdevicelink.streaming.video.VideoStreamingParameters;
+import com.smartdevicelink.transport.BaseTransportConfig;
+
+public interface ISdlProtocol extends IProtocolListener {
+
+ byte getSessionId();
+
+ void shutdown(String info);
+
+ void onTransportDisconnected(String info, boolean altTransportAvailable, BaseTransportConfig transportConfig); //FIXME config
+
+ SdlSecurityBase getSdlSecurity();
+
+ VideoStreamingParameters getDesiredVideoParams();
+
+ void setAcceptedVideoParams(VideoStreamingParameters acceptedVideoParams);
+
+ void stopStream(SessionType serviceType);
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/ISecondaryTransportListener.java b/base/src/main/java/com/smartdevicelink/protocol/ISecondaryTransportListener.java
new file mode 100644
index 000000000..17db7ad11
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/ISecondaryTransportListener.java
@@ -0,0 +1,8 @@
+package com.smartdevicelink.protocol;
+
+import com.smartdevicelink.transport.utl.TransportRecord;
+
+public interface ISecondaryTransportListener {
+ void onConnectionSuccess(TransportRecord transportRecord);
+ void onConnectionFailure();
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java b/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
new file mode 100644
index 000000000..6fbbb0053
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/ProtocolMessage.java
@@ -0,0 +1,151 @@
+package com.smartdevicelink.protocol;
+
+import com.smartdevicelink.protocol.enums.MessageType;
+import com.smartdevicelink.protocol.enums.SessionType;
+
+public class ProtocolMessage {
+ private byte version = 1;
+ private SessionType _sessionType = SessionType.RPC;
+ private MessageType _messageType = MessageType.UNDEFINED;
+ private byte _sessionID = 0;
+ private byte _rpcType;
+ private int _functionID;
+ private int _correlationID;
+ private int _jsonSize;
+ private boolean payloadProtected = false;
+
+ int priorityCoefficient = 0;
+
+ private byte[] _data = null;
+ private byte[] _bulkData = null;
+
+ public ProtocolMessage() {}
+
+ public byte getVersion() {
+ return version;
+ }
+
+ public void setVersion(byte version) {
+ this.version = version;
+ }
+
+ public byte getSessionID() {
+ return _sessionID;
+ }
+
+ public void setSessionID(byte sessionID) {
+ this._sessionID = sessionID;
+ }
+
+ public byte[] getData() {
+ return _data;
+ }
+
+ public void setData(byte[] data) {
+ this._data = data;
+ this._jsonSize = data.length;
+ }
+
+ public void setData(byte[] data, int length) {
+ setData(data, 0, length);
+ }
+
+ public void setData(byte[] data, int offset, int length) {
+ if (this._data != null)
+ this._data = null;
+ this._data = new byte[length];
+ System.arraycopy(data, offset, this._data, 0, length);
+ this._jsonSize = 0;
+ }
+
+ public byte[] getBulkData() {
+ return _bulkData;
+ }
+
+ public void setBulkDataNoCopy(byte[] bulkData) {
+ this._bulkData = bulkData;
+ }
+
+ public void setBulkData(byte[] bulkData) {
+ if (this._bulkData != null)
+ this._bulkData = null;
+ this._bulkData = new byte[bulkData.length];
+ System.arraycopy(bulkData, 0, this._bulkData, 0, bulkData.length);
+ //this._bulkData = bulkData;
+ }
+
+ public void setBulkData(byte[] bulkData, int length) {
+ if (this._bulkData != null)
+ this._bulkData = null;
+ this._bulkData = new byte[length];
+ System.arraycopy(bulkData, 0, this._bulkData, 0, length);
+ //this._bulkData = bulkData;
+ }
+
+ public SessionType getSessionType() {
+ return _sessionType;
+ }
+
+ public void setSessionType(SessionType sessionType) {
+ this._sessionType = sessionType;
+ }
+
+ public MessageType getMessageType() {
+ return _messageType;
+ }
+
+ public void setMessageType(MessageType messageType) {
+ this._messageType = messageType;
+ }
+
+ public byte getRPCType() {
+ return _rpcType;
+ }
+
+ public void setRPCType(byte _rpcType) {
+ this._rpcType = _rpcType;
+ }
+
+ public int getFunctionID() {
+ return _functionID;
+ }
+
+ public void setFunctionID(int _functionID) {
+ this._functionID = _functionID;
+ }
+
+ public int getCorrID() {
+ return _correlationID;
+ }
+
+ public void setCorrID(int _correlationID) {
+ this._correlationID = _correlationID;
+ }
+
+ public int getJsonSize() {
+ return _jsonSize;
+ }
+
+ public void setJsonSize(int _jsonSize) {
+ this._jsonSize = _jsonSize;
+ }
+
+ public void setPayloadProtected(boolean bVal) {
+ payloadProtected = bVal;
+ }
+
+ public boolean getPayloadProtected() {
+ return payloadProtected;
+ }
+
+ /**
+ * Set the priority for this packet. The lower the number the higher the priority. <br>0 is the highest priority and the default.
+ * @param priority
+ */
+ public void setPriorityCoefficient(int priority){
+ this.priorityCoefficient = priority;
+ }
+ public int getPrioirtyCoefficient(){
+ return this.priorityCoefficient;
+ }
+} // end-class \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SdlPacket.java b/base/src/main/java/com/smartdevicelink/protocol/SdlPacket.java
new file mode 100644
index 000000000..40ec54fb5
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/SdlPacket.java
@@ -0,0 +1,422 @@
+package com.smartdevicelink.protocol;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+
+import com.livio.BSON.BsonEncoder;
+import com.smartdevicelink.protocol.enums.FrameType;
+import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.TransportRecord;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class SdlPacket implements Parcelable{
+
+
+ public static final int HEADER_SIZE = 12;
+ public static final int HEADER_SIZE_V1 = 8;//Backwards
+
+ private static final int ENCRYPTION_MASK = 0x08; //4th lowest bit
+
+ public static final int FRAME_TYPE_CONTROL = 0x00;
+ public static final int FRAME_TYPE_SINGLE = 0x01;
+ public static final int FRAME_TYPE_FIRST = 0x02;
+ public static final int FRAME_TYPE_CONSECUTIVE = 0x03;
+
+ /*
+ * Service Type
+ */
+ public static final int SERVICE_TYPE_CONTROL = 0x00;
+ //RESERVED 0x01 - 0x06
+ public static final int SERVICE_TYPE_RPC = 0x07;
+ //RESERVED 0x08 - 0x09
+ public static final int SERVICE_TYPE_PCM = 0x0A;
+ public static final int SERVICE_TYPE_VIDEO = 0x0B;
+ //RESERVED 0x0C - 0x0E
+ public static final int SERVICE_TYPE_BULK_DATA = 0x0F;
+ //RESERVED 0x10 - 0xFF
+
+
+ /*
+ * Frame Info
+ */
+ //Control Frame Info
+ public static final int FRAME_INFO_HEART_BEAT = 0x00;
+ public static final int FRAME_INFO_START_SERVICE = 0x01;
+ public static final int FRAME_INFO_START_SERVICE_ACK = 0x02;
+ public static final int FRAME_INFO_START_SERVICE_NAK = 0x03;
+ public static final int FRAME_INFO_END_SERVICE = 0x04;
+ public static final int FRAME_INFO_END_SERVICE_ACK = 0x05;
+ public static final int FRAME_INFO_END_SERVICE_NAK = 0x06;
+ public static final int FRAME_INFO_REGISTER_SECONDARY_TRANSPORT = 0x07;
+ public static final int FRAME_INFO_REGISTER_SECONDARY_TRANSPORT_ACK = 0x08;
+ public static final int FRAME_INFO_REGISTER_SECONDARY_TRANSPORT_NAK = 0x09;
+ //0x0A-0xFC are reserved
+ public static final int FRAME_INFO_TRANSPORT_EVENT_UPDATE = 0xFD;
+ public static final int FRAME_INFO_SERVICE_DATA_ACK = 0xFE;
+ public static final int FRAME_INFO_HEART_BEAT_ACK = 0xFF;
+
+ public static final int FRAME_INFO_FINAL_CONNESCUTIVE_FRAME = 0x00;
+
+ //Most others
+ public static final int FRAME_INFO_RESERVED = 0x00;
+
+
+ int version;
+ boolean encryption;
+ int frameType;
+ int serviceType;
+ int frameInfo;
+ int sessionId;
+ int dataSize;
+ int messageId;
+ int priorityCoefficient;
+ byte[] payload = null;
+ HashMap<String, Object> bsonPayload;
+
+ int messagingVersion = 1;
+ TransportRecord transportRecord;
+
+ public SdlPacket(int version, boolean encryption, int frameType,
+ int serviceType, int frameInfo, int sessionId,
+ int dataSize, int messageId, byte[] payload) {
+ this.version = version;
+ this.encryption = encryption;
+ this.frameType = frameType;
+ this.serviceType = serviceType;
+ this.frameInfo = frameInfo;
+ this.sessionId = sessionId;
+ this.dataSize = dataSize;
+ this.messageId = messageId;
+ this.priorityCoefficient = 0;
+ if(payload!=null){
+ this.payload = new byte[payload.length];
+ System.arraycopy(payload, 0, this.payload, 0, payload.length);
+ }
+ }
+
+ public SdlPacket(int version, boolean encryption, int frameType,
+ int serviceType, int frameInfo, int sessionId,
+ int dataSize, int messageId, byte[] payload, int offset,int bytesToWrite) {
+ this.version = version;
+ this.encryption = encryption;
+ this.frameType = frameType;
+ this.serviceType = serviceType;
+ this.frameInfo = frameInfo;
+ this.sessionId = sessionId;
+ this.dataSize = dataSize;
+ this.messageId = messageId;
+ this.priorityCoefficient = 0;
+ if(payload!=null){
+ this.payload = new byte[bytesToWrite];
+ System.arraycopy(payload, offset, this.payload, 0, bytesToWrite);
+ }
+ }
+ /**
+ * This constructor is available as a protected method. A few defaults have been set, however a few things <b>MUST</b> be set before use. The rest will "work"
+ * however, it won't be valid data.
+ *
+ * <p>Frame Type
+ * <p>Service Type
+ * <p>Frame Info
+ * <p>
+ */
+ protected SdlPacket(){
+ //Package only empty constructor
+ //TODO add defaults
+ this.version = 1;
+ this.encryption = false;
+ this.frameType = -1; //This NEEDS to be set
+ this.serviceType = -1;
+ this.frameInfo = -1;
+ this.sessionId = 0;
+ this.dataSize = 0;
+ this.messageId = 0;
+
+ }
+
+ /**
+ * Creates a new packet based on previous packet definitions
+ * @param packet
+ */
+ protected SdlPacket(SdlPacket packet){
+ this.version = packet.version;
+ this.encryption = packet.encryption;
+ this.frameType = packet.frameType;
+ this.serviceType = packet.serviceType;
+ this.frameInfo = packet.frameInfo;
+ this.sessionId = packet.sessionId;
+ this.dataSize = 0;
+ this.messageId = 0;
+ }
+
+ public int getVersion() {
+ return version;
+ }
+
+ public boolean isEncrypted() {
+ return encryption;
+ }
+
+ public FrameType getFrameType() {
+ switch(frameType){
+ case FRAME_TYPE_CONTROL:
+ return FrameType.Control;
+ case FRAME_TYPE_FIRST:
+ return FrameType.First;
+ case FRAME_TYPE_CONSECUTIVE:
+ return FrameType.Consecutive;
+ case FRAME_TYPE_SINGLE:
+ default:
+ return FrameType.Single;
+ }
+ }
+
+ public int getServiceType() {
+ return serviceType;
+ }
+
+ public int getFrameInfo() {
+ return frameInfo;
+ }
+
+ public int getSessionId() {
+ return sessionId;
+ }
+
+ public int getMessageId() {
+ return messageId;
+ }
+
+ public long getDataSize() {
+ return dataSize;
+ }
+
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ public byte[] constructPacket() {
+ if (bsonPayload != null && !bsonPayload.isEmpty()) {
+ byte[] bsonBytes = BsonEncoder.encodeToBytes(bsonPayload);
+ if(bsonBytes != null) {
+ payload = bsonBytes;
+ dataSize = bsonBytes.length;
+ }
+ }
+ return constructPacket(version, encryption, frameType,
+ serviceType, frameInfo, sessionId,
+ dataSize, messageId, payload);
+ }
+ public void setPayload(byte[] bytes){
+ this.payload = bytes;
+ }
+ /**
+ * Set the priority for this packet. The lower the number the higher the priority. <br>0 is the highest priority and the default.
+ * @param priority
+ */
+ public void setPriorityCoefficient(int priority){
+ this.priorityCoefficient = priority;
+ }
+ public int getPrioirtyCoefficient(){
+ return this.priorityCoefficient;
+ }
+
+ public void setTransportRecord(TransportRecord transportRecord){
+ this.transportRecord = transportRecord;
+ }
+
+ public TransportRecord getTransportRecord() {
+ return this.transportRecord;
+ }
+
+ /**
+ * This method takes in the various components to the SDL packet structure and creates a new byte array that can be sent via the transport
+ * @param version
+ * @param encryption
+ * @param frameType
+ * @param serviceType
+ * @param controlFrameInfo
+ * @param sessionId
+ * @param dataSize
+ * @param messageId
+ * @param payload
+ * @return
+ */
+ public static byte[] constructPacket(int version, boolean encryption, int frameType,
+ int serviceType, int controlFrameInfo, int sessionId,
+ int dataSize, int messageId, byte[] payload){
+
+ ByteBuffer builder;
+ switch(version){
+ case 1:
+ builder = ByteBuffer.allocate(HEADER_SIZE_V1 + dataSize);
+ break;
+ default:
+ builder = ByteBuffer.allocate(HEADER_SIZE + dataSize);
+ break;
+ }
+
+ builder.put((byte)((version<<4) + getEncryptionBit(encryption) + frameType));
+ builder.put((byte)serviceType);
+ builder.put((byte)controlFrameInfo);
+ builder.put((byte)sessionId);
+
+ builder.put((byte)((dataSize&0xFF000000)>>24));
+ builder.put((byte)((dataSize&0x00FF0000)>>16));
+ builder.put((byte)((dataSize&0x0000FF00)>>8));
+ builder.put((byte)((dataSize&0x000000FF)));
+
+ if(version>1){ //Version 1 did not include this part of the header
+ builder.put((byte)((messageId&0xFF000000)>>24));
+ builder.put((byte)((messageId&0x00FF0000)>>16));
+ builder.put((byte)((messageId&0x0000FF00)>>8));
+ builder.put((byte)((messageId&0x000000FF)));
+ }
+
+ if(payload!=null && payload.length>0){
+ builder.put(payload);
+ }
+
+ return builder.array();
+ }
+
+
+ public static int getEncryptionBit(boolean encryption){
+ if(encryption){
+ return ENCRYPTION_MASK;
+ }else{
+ return 0;
+ }
+ }
+
+
+
+@Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("***** Sdl Packet ******");
+ builder.append( "\nVersion: " +version);
+ builder.append( "\nEncryption: " +encryption);
+ builder.append( "\nFrameType: " +frameType);
+ builder.append( "\nServiceType: " +serviceType);
+ builder.append( "\nFrameInfo: " +frameInfo);
+ builder.append( "\nSessionId: " +sessionId);
+ builder.append( "\nDataSize: " +dataSize);
+ if(version>1){
+ builder.append( "\nMessageId: " +messageId);
+ }
+ builder.append("\n***** Sdl Packet End******");
+
+
+ return builder.toString();
+ }
+
+ public void setMessagingVersion(int version){
+ this.messagingVersion = version;
+ }
+
+
+
+ /* ***************************************************************************************************************************************************
+ * *********************************************************** Parceable Overrides *****************************************************************
+ *****************************************************************************************************************************************************/
+
+
+
+ //I think this is FIFO...right?
+ public SdlPacket(Parcel p) {
+ this.version = p.readInt();
+ this.encryption = (p.readInt() == 0) ? false : true;
+ this.frameType = p.readInt();
+ this.serviceType = p.readInt();
+ this.frameInfo = p.readInt();
+ this.sessionId = p.readInt();
+ this.dataSize = p.readInt();
+ this.messageId = p.readInt();
+ if(p.readInt() == 1){ //We should have a payload attached
+ payload = new byte[dataSize];
+ p.readByteArray(payload);
+ }
+
+ this.priorityCoefficient = p.readInt();
+
+ if(p.dataAvail() > 0) {
+ messagingVersion = p.readInt();
+ if(messagingVersion >= 2) {
+ if (p.readInt() == 1) { //We should have a transport type attached
+ this.transportRecord = (TransportRecord) p.readParcelable(TransportRecord.class.getClassLoader());
+ }
+ }
+ }
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+
+ dest.writeInt(version);
+ dest.writeInt(encryption? 1 : 0);
+ dest.writeInt(frameType);
+ dest.writeInt(serviceType);
+ dest.writeInt(frameInfo);
+ dest.writeInt(sessionId);
+ dest.writeInt(dataSize);
+ dest.writeInt(messageId);
+ dest.writeInt(payload!=null? 1 : 0);
+ if(payload!=null){
+ dest.writeByteArray(payload);
+ }
+ dest.writeInt(priorityCoefficient);
+
+ ///Additions after initial creation
+ if(messagingVersion > 1){
+ dest.writeInt(messagingVersion);
+
+ dest.writeInt(transportRecord!=null? 1 : 0);
+ if(transportRecord != null){
+ dest.writeParcelable(transportRecord,0);
+ }
+ }
+
+ }
+
+ public static final Parcelable.Creator<SdlPacket> CREATOR = new Parcelable.Creator<SdlPacket>() {
+ public SdlPacket createFromParcel(Parcel in) {
+ return new SdlPacket(in);
+ }
+
+ @Override
+ public SdlPacket[] newArray(int size) {
+ return new SdlPacket[size];
+ }
+
+ };
+
+ public void putTag(String tag, Object data){
+ if(bsonPayload == null){
+ bsonPayload = new HashMap<>();
+ }
+ bsonPayload.put(tag, data);
+ }
+
+ public Object getTag(String tag){
+ if(payload == null){
+ return null;
+ }else if(bsonPayload == null || bsonPayload.isEmpty()){
+ bsonPayload = BsonEncoder.decodeFromBytes(payload);
+ }
+
+ if(bsonPayload == null){
+ return null;
+ }
+
+ return bsonPayload.get(tag);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SdlPacketFactory.java b/base/src/main/java/com/smartdevicelink/protocol/SdlPacketFactory.java
new file mode 100644
index 000000000..31898175c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/SdlPacketFactory.java
@@ -0,0 +1,97 @@
+package com.smartdevicelink.protocol;
+
+import com.smartdevicelink.protocol.enums.FrameDataControlFrameType;
+import com.smartdevicelink.protocol.enums.SessionType;
+
+public class SdlPacketFactory {
+
+ /*
+ * public SdlPacket(int version, boolean compression, int frameType,
+ int serviceType, int frameInfo, int sessionId,
+ int dataSize, int messageId, byte[] payload) {
+ */
+ public static SdlPacket createStartSession(SessionType serviceType, int messageID, byte version, byte sessionID, boolean encrypted) {
+ SdlPacket packet = new SdlPacket(version,encrypted,SdlPacket.FRAME_TYPE_CONTROL,
+ serviceType.getValue(),SdlPacket.FRAME_INFO_START_SERVICE,sessionID,
+ 0,messageID,null);
+
+ return packet;
+ }
+
+ public static SdlPacket createHeartbeat(SessionType serviceType, byte sessionID, byte version) {
+
+ return new SdlPacket(version,false,SdlPacket.FRAME_TYPE_CONTROL,
+ serviceType.getValue(),FrameDataControlFrameType.Heartbeat.value(),sessionID,
+ 0,0,null);
+
+ }
+
+ public static SdlPacket createHeartbeatACK(SessionType serviceType, byte sessionID, byte version) {
+ return new SdlPacket(version,false,SdlPacket.FRAME_TYPE_CONTROL,
+ serviceType.getValue(),FrameDataControlFrameType.HeartbeatACK.value(),sessionID,
+ 0,0,null);
+ }
+
+ public static SdlPacket createStartSessionACK(SessionType serviceType, byte sessionID, int messageID, byte version) {
+
+ return new SdlPacket(version,false,SdlPacket.FRAME_TYPE_CONTROL,
+ serviceType.getValue(),FrameDataControlFrameType.StartSessionACK.value(),sessionID,
+ 0,messageID,null);
+
+ }
+
+ public static SdlPacket createStartSessionNACK(SessionType serviceType, byte sessionID, int messageID, byte version) {
+
+ return new SdlPacket(version,false,SdlPacket.FRAME_TYPE_CONTROL,
+ serviceType.getValue(),SdlPacket.FRAME_INFO_START_SERVICE_NAK,sessionID,
+ 0,messageID,null);
+ }
+
+ public static SdlPacket createEndSession(SessionType serviceType, byte sessionID, int messageID, byte version, byte[] payload) {
+ return new SdlPacket(version,false,SdlPacket.FRAME_TYPE_CONTROL,
+ serviceType.getValue(),SdlPacket.FRAME_INFO_END_SERVICE,sessionID,
+ payload.length,messageID,payload);
+ }
+
+ public static SdlPacket createSingleSendData(SessionType serviceType, byte sessionID,
+ int dataLength, int messageID, byte version, byte[] payload, boolean encrypted) {
+
+ return new SdlPacket(version,encrypted,SdlPacket.FRAME_TYPE_SINGLE,
+ serviceType.getValue(),0,sessionID,
+ payload.length,messageID,payload);
+ }
+
+ public static SdlPacket createMultiSendDataFirst(SessionType serviceType, byte sessionID,
+ int messageID, byte version, byte[] payload, boolean encrypted) {
+
+ return new SdlPacket(version,encrypted,SdlPacket.FRAME_TYPE_FIRST,
+ serviceType.getValue(),0,sessionID,
+ 8,messageID,payload);
+
+ }
+
+ public static SdlPacket createMultiSendDataRest(SessionType serviceType, byte sessionID,
+ int dataLength, byte frameSequenceNumber, int messageID, byte version, byte[] payload,int offset,int length, boolean encrypted) {
+
+ return new SdlPacket(version,encrypted,SdlPacket.FRAME_TYPE_CONSECUTIVE,
+ serviceType.getValue(),frameSequenceNumber,sessionID,
+ length,messageID,payload,offset,length);
+ }
+
+ public static SdlPacket createRegisterSecondaryTransport(byte sessionID, byte version) {
+ return new SdlPacket(version, false, SdlPacket.FRAME_TYPE_CONTROL,
+ SessionType.CONTROL.getValue(), SdlPacket.FRAME_INFO_REGISTER_SECONDARY_TRANSPORT,
+ sessionID, 0, 0x01, null);
+ }
+
+ public static BinaryFrameHeader createBinaryFrameHeader(byte rpcType, int functionID, int corrID, int jsonSize) {
+ BinaryFrameHeader msg = new BinaryFrameHeader();
+ msg.setRPCType(rpcType);
+ msg.setFunctionID(functionID);
+ msg.setCorrID(corrID);
+ msg.setJsonSize(jsonSize);
+
+ return msg;
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/protocol/SdlProtocol.java b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocol.java
new file mode 100644
index 000000000..52feaadaa
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/protocol/SdlProtocol.java
@@ -0,0 +1,1432 @@
+/*
+ * Copyright (c) 2018 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.protocol;
+
+import android.util.Log;
+
+import com.smartdevicelink.exception.SdlException;
+import com.smartdevicelink.exception.SdlExceptionCause;
+import com.smartdevicelink.protocol.enums.ControlFrameTags;
+import com.smartdevicelink.protocol.enums.FrameDataControlFrameType;
+import com.smartdevicelink.protocol.enums.FrameType;
+import com.smartdevicelink.protocol.enums.MessageType;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.rpc.ImageResolution;
+import com.smartdevicelink.proxy.rpc.VideoStreamingFormat;
+import com.smartdevicelink.proxy.rpc.enums.VideoStreamingCodec;
+import com.smartdevicelink.proxy.rpc.enums.VideoStreamingProtocol;
+import com.smartdevicelink.security.SdlSecurityBase;
+import com.smartdevicelink.streaming.video.VideoStreamingParameters;
+import com.smartdevicelink.transport.TransportConstants;
+import com.smartdevicelink.transport.TransportManager;
+import com.smartdevicelink.transport.WebSocketServerConfig;
+import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.TransportRecord;
+import com.smartdevicelink.util.BitConverter;
+import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.Version;
+
+import java.io.ByteArrayOutputStream;
+import java.util.*;
+
+@SuppressWarnings("WeakerAccess")
+public class SdlProtocol {
+ private static final String TAG ="SdlProtocol";
+ private final static String FailurePropagating_Msg = "Failure propagating ";
+
+ private static final int TLS_MAX_RECORD_SIZE = 16384;
+
+ private static final int PRIMARY_TRANSPORT_ID = 1;
+ private static final int SECONDARY_TRANSPORT_ID = 2;
+
+ /**
+ * Original header size based on version 1.0.0 only
+ */
+ public static final int V1_HEADER_SIZE = 8;
+ /**
+ * Larger header size that is used by versions 2.0.0 and up
+ */
+ public static final int V2_HEADER_SIZE = 12;
+
+ //If increasing MAX PROTOCOL VERSION major version, make sure to alter it in SdlPsm
+ private static final Version MAX_PROTOCOL_VERSION = new Version("5.1.0");
+
+ public static final int V1_V2_MTU_SIZE = 1500;
+ public static final int V3_V4_MTU_SIZE = 131072;
+
+ private static final List<SessionType> HIGH_BANDWIDTH_SERVICES
+ = Arrays.asList(SessionType.NAV, SessionType.PCM);
+
+ // Lock to ensure all frames are sent uninterrupted
+ private final Object FRAME_LOCK = new Object();
+
+ private final ISdlProtocol iSdlProtocol;
+ private final WebSocketServerConfig transportConfig;
+ private final Hashtable<Integer, MessageFrameAssembler> _assemblerForMessageID = new Hashtable<>();
+ private final Hashtable<Byte, Object> _messageLocks = new Hashtable<>();
+ private final HashMap<SessionType, Long> mtus = new HashMap<>();
+ private final HashMap<SessionType, TransportRecord> activeTransports = new HashMap<>();
+ private final Map<TransportType, List<ISecondaryTransportListener>> secondaryTransportListeners = new HashMap<>();
+
+
+ private TransportManager transportManager;
+ private Version protocolVersion = new Version("1.0.0");
+ private int hashID = 0;
+ private int messageID = 0;
+ private int headerSize = V1_HEADER_SIZE;
+
+ /**
+ * Requested transports for primary and secondary
+ */
+ List<TransportType> requestedPrimaryTransports, requestedSecondaryTransports;
+
+ /**
+ * List of secondary transports supported by the module
+ */
+ List<TransportType> supportedSecondaryTransports;
+
+ /**
+ * Holds the priority of transports for a specific service when that service can be started
+ * on a primary or secondary transport.
+ */
+ Map<SessionType, List<Integer>> transportPriorityForServiceMap;
+ boolean requiresHighBandwidth;
+ Map<TransportType, Object> secondaryTransportParams;
+ TransportRecord connectedPrimaryTransport;
+
+
+ @SuppressWarnings("ConstantConditions")
+ public SdlProtocol( ISdlProtocol iSdlProtocol, WebSocketServerConfig config) { //FIXME to change config
+ if (iSdlProtocol == null ) {
+ throw new IllegalArgumentException("Provided protocol listener interface reference is null");
+ } // end-if
+
+ this.iSdlProtocol = iSdlProtocol;
+ this.transportConfig = config;
+ this.requestedPrimaryTransports = Collections.singletonList(TransportType.WEB_SOCKET_SERVER); //FIXME this.transportConfig.getPrimaryTransports();
+ this.requestedSecondaryTransports = Collections.emptyList();//FIXME this.transportConfig.getSecondaryTransports();
+ this.requiresHighBandwidth = false; //FIXME this.transportConfig.requiresHighBandwidth();
+ this.transportManager = new TransportManager(transportConfig, transportEventListener);
+
+
+ mtus.put(SessionType.RPC, (long) (V1_V2_MTU_SIZE - headerSize));
+ } // end-ctor
+
+
+ public void start(){
+ transportManager.start();
+
+ }
+ /**
+ * Retrieves the max payload size for a packet to be sent to the module
+ * @return the max transfer unit
+ */
+ public int getMtu(){
+ return mtus.get(SessionType.RPC).intValue();
+ }
+
+ public long getMtu(SessionType type){
+ Long mtu = mtus.get(type);
+ if(mtu == null){
+ mtu = mtus.get(SessionType.RPC);
+ }
+ return mtu;
+ }
+
+ public boolean isConnected(){
+ return transportManager != null && transportManager.isConnected(null,null);
+ }
+
+ /**
+ * Resets the protocol to init status
+ */
+ protected void reset(){
+ protocolVersion = new Version("1.0.0");
+ hashID = 0;
+ messageID = 0;
+ headerSize = V1_HEADER_SIZE;
+ this.activeTransports.clear();
+ this.mtus.clear();
+ mtus.put(SessionType.RPC, (long) (V1_V2_MTU_SIZE - headerSize));
+ this.secondaryTransportParams = null;
+ this._assemblerForMessageID.clear();
+ this._messageLocks.clear();
+ }
+
+ /**
+ * For logging purposes, prints active services on each connected transport
+ */
+ protected void printActiveTransports(){
+ StringBuilder activeTransportString = new StringBuilder();
+ activeTransportString.append("Active transports --- \n");
+
+ for(Map.Entry entry : activeTransports.entrySet()){
+ String sessionString = null;
+ if(entry.getKey().equals(SessionType.NAV)) {
+ sessionString = "NAV";
+ }else if(entry.getKey().equals(SessionType.PCM)) {
+ sessionString = "PCM";
+ }else if(entry.getKey().equals(SessionType.RPC)) {
+ sessionString = "RPC";
+ }
+ if(sessionString != null){
+ activeTransportString.append("Session: ");
+
+ activeTransportString.append(sessionString);
+ activeTransportString.append(" Transport: ");
+ activeTransportString.append(entry.getValue().toString());
+ activeTransportString.append("\n");
+ }
+ }
+ Log.d(TAG, activeTransportString.toString());
+ }
+
+ protected void printSecondaryTransportDetails(List<String> secondary, List<Integer> audio, List<Integer> video){
+ StringBuilder secondaryDetailsBldr = new StringBuilder();
+ secondaryDetailsBldr.append("Checking secondary transport details \n");
+
+ if(secondary != null){
+ secondaryDetailsBldr.append("Supported secondary transports: ");
+ for(String s : secondary){
+ secondaryDetailsBldr.append(" ").append(s);
+ }
+ secondaryDetailsBldr.append("\n");
+ }else{
+ Log.d(TAG, "Supported secondary transports list is empty!");
+ }
+ if(audio != null){
+ secondaryDetailsBldr.append("Supported audio transports: ");
+ for(int a : audio){
+ secondaryDetailsBldr.append(" ").append(a);
+ }
+ secondaryDetailsBldr.append("\n");
+ }
+ if(video != null){
+ secondaryDetailsBldr.append("Supported video transports: ");
+ for(int v : video){
+ secondaryDetailsBldr.append(" ").append(v);
+ }
+ secondaryDetailsBldr.append("\n");
+ }
+
+ Log.d(TAG, secondaryDetailsBldr.toString());
+ }
+
+
+ private TransportRecord getTransportForSession(SessionType type){
+ return activeTransports.get(type);
+ }
+
+ private void setTransportPriorityForService(SessionType serviceType, List<Integer> order){
+ if(transportPriorityForServiceMap == null){
+ transportPriorityForServiceMap = new HashMap<>();
+ }
+ this.transportPriorityForServiceMap.put(serviceType, order);
+ for(SessionType service : HIGH_BANDWIDTH_SERVICES){
+ if (transportPriorityForServiceMap.get(service) != null
+ && transportPriorityForServiceMap.get(service).contains(PRIMARY_TRANSPORT_ID)) {
+ if(connectedPrimaryTransport != null) {
+ activeTransports.put(service, connectedPrimaryTransport);
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles when a secondary transport can be used to start services on or when the request as failed.
+ * @param transportRecord the transport type that the event has taken place on
+ * @param registered if the transport was successfully registered on
+ */
+ private void handleSecondaryTransportRegistration(TransportRecord transportRecord, boolean registered){
+ if(registered) {
+ //Session has been registered on secondary transport
+ Log.d(TAG, transportRecord.getType().toString() + " transport was registered!");
+ if (supportedSecondaryTransports.contains(transportRecord.getType())) {
+ // If the transport type that is now available to be used it should be checked
+ // against the list of services that might be able to be started on it
+
+ for(SessionType secondaryService : HIGH_BANDWIDTH_SERVICES){
+ if (transportPriorityForServiceMap.containsKey(secondaryService)) {
+ // If this service type has extra information from the RPC StartServiceACK
+ // parse through it to find which transport should be used to start this
+ // specific service type
+ for(int transportNum : transportPriorityForServiceMap.get(secondaryService)){
+ if(transportNum == PRIMARY_TRANSPORT_ID){
+ break; // Primary is favored for this service type, break out...
+ }else if(transportNum == SECONDARY_TRANSPORT_ID){
+ // The secondary transport can be used to start this service
+ activeTransports.put(secondaryService, transportRecord);
+ break;
+ }
+ }
+ }
+ }
+ }
+ }else{
+ Log.d(TAG, transportRecord.toString() + " transport was NOT registered!");
+ }
+ //Notify any listeners for this secondary transport
+ List<ISecondaryTransportListener> listenerList = secondaryTransportListeners.remove(transportRecord.getType());
+ if(listenerList != null){
+ for(ISecondaryTransportListener listener : listenerList){
+ if(registered) {
+ listener.onConnectionSuccess(transportRecord);
+ }else{
+ listener.onConnectionFailure();
+ }
+ }
+ }
+
+ if(DebugTool.isDebugEnabled()){
+ printActiveTransports();
+ }
+ }
+
+ private void onTransportsConnectedUpdate(List<TransportRecord> transports){
+ //Log.d(TAG, "Connected transport update");
+
+ //Temporary: this logic should all be changed to handle multiple transports of the same type
+ ArrayList<TransportType> connectedTransports = new ArrayList<>();
+ if(transports != null) {
+ for (TransportRecord record : transports) {
+ connectedTransports.add(record.getType());
+ }
+ }
+
+ if(connectedPrimaryTransport != null && !connectedTransports.contains(connectedPrimaryTransport.getType())){
+ //The primary transport being used is no longer part of the connected transports
+ //The transport manager callbacks should handle the disconnect code
+ connectedPrimaryTransport = null;
+ notifyDevTransportListener();
+ return;
+ }
+
+ if(activeTransports.get(SessionType.RPC) == null){
+ //There is no currently active transport for the RPC service meaning no primary transport
+ TransportRecord preferredPrimaryTransport = getPreferredTransport(requestedPrimaryTransports,transports);
+ if(preferredPrimaryTransport != null) {
+ connectedPrimaryTransport = preferredPrimaryTransport;
+ startService(SessionType.RPC, (byte) 0x00, false);
+ }else{
+ onTransportNotAccepted("No transports match requested primary transport");
+ }
+ //Return to that the developer does not receive the transport callback at this time
+ // as it is better to wait until the RPC service is registered and secondary transport
+ //information is available
+ return;
+ }else if(secondaryTransportListeners != null
+ && transports != null
+ && iSdlProtocol!= null){
+ // Check to see if there is a listener for a given transport.
+ // If a listener exists, it can be assumed that the transport should be registered on
+ for(TransportRecord record: transports){
+ if(secondaryTransportListeners.get(record.getType()) != null
+ && !secondaryTransportListeners.get(record.getType()).isEmpty()){
+ registerSecondaryTransport(iSdlProtocol.getSessionId(), record);
+ }
+ }
+ }
+ //Update the developer that a new transport has become available
+ notifyDevTransportListener();
+ }
+
+
+ /**
+ * Check to see if a transport is available to start/use the supplied service.
+ * @param serviceType the session that should be checked for transport availability
+ * @return true if there is either a supported
+ * transport currently connected or a transport is
+ * available to connect with for the supplied service type.
+ * <br>false if there is no
+ * transport connected to support the service type in question and
+ * no possibility in the foreseeable future.
+ */
+ public boolean isTransportForServiceAvailable( SessionType serviceType){
+ if(connectedPrimaryTransport == null){
+ //If there is no connected primary then there is no transport available for any service
+ return false;
+ }else if(activeTransports!= null && activeTransports.containsKey(serviceType)){
+ //There is an active transport that this service can be used on
+ //This should catch RPC, Bulk, and Control service types
+ return true;
+ }
+
+ if(transportPriorityForServiceMap != null) {
+ List<Integer> transportPriority = transportPriorityForServiceMap.get(serviceType);
+
+ if (transportPriority != null && !transportPriority.isEmpty()) {
+ if (transportPriority.contains(PRIMARY_TRANSPORT_ID)) {
+ //If the transport priority for this service type contains primary then
+ // the service can be used/started
+ return true;
+ } else if (transportPriority.contains(SECONDARY_TRANSPORT_ID)) {
+ //This would mean only secondary transport is supported for this service
+ return isSecondaryTransportAvailable(false);
+ }
+ }
+ }
+
+ //No transport priority for this service type
+ if(connectedPrimaryTransport.getType() == TransportType.USB || connectedPrimaryTransport.getType() == TransportType.TCP){
+ //Since the only service type that should reach this point are ones that require a high
+ //bandwidth, true can be returned if the primary transport is a high bandwidth transport
+ return true;
+ }else{
+ //Since the only service type that should reach this point are ones that require a high
+ //bandwidth, true can be returned if a secondary transport is a high bandwidth transport
+ return isSecondaryTransportAvailable(true);
+ }
+ }
+
+ /**
+ * Checks to see if a secondary transport is available for this session
+ * @param onlyHighBandwidth if only high bandwidth transports should be included in this check
+ * @return true if any connected or potential transport meets the criteria to be a secondary
+ * transport
+ */
+ private boolean isSecondaryTransportAvailable(boolean onlyHighBandwidth){
+ if (supportedSecondaryTransports != null) {
+ for (TransportType supportedSecondary : supportedSecondaryTransports) {
+ if(!onlyHighBandwidth || supportedSecondary == TransportType.USB || supportedSecondary == TransportType.TCP) {
+ if (transportManager.isConnected(supportedSecondary, null)) {
+ //A supported secondary transport is already connected
+ return true;
+ } else if (secondaryTransportParams != null && secondaryTransportParams.containsKey(supportedSecondary)) {
+ //A secondary transport is available to connect to
+ return true;
+ }
+ }
+ }
+ }
+ // No supported secondary transports
+ return false;
+ }
+
+
+ /**
+ * If there was a TransportListener attached to the supplied multiplex config, this method will
+ * call the onTransportEvent method.
+ */
+ private void notifyDevTransportListener (){
+ //FIXME
+ /* if(transportConfig.getTransportListener() != null && transportManager != null) {
+ transportConfig.getTransportListener().onTransportEvent(transportManager.getConnectedTransports(), isTransportForServiceAvailable(SessionType.PCM),isTransportForServiceAvailable(SessionType.NAV));
+ }*/
+ }
+
+ /**
+ * Retrieves the preferred transport for the given connected transport
+ * @param preferredList the list of preferred transports (primary or secondary)
+ * @param connectedTransports the current list of connected transports
+ * @return the preferred connected transport
+ */
+ private TransportRecord getPreferredTransport(List<TransportType> preferredList, List<TransportRecord> connectedTransports) {
+ for (TransportType transportType : preferredList) {
+ for(TransportRecord record: connectedTransports) {
+ if (record.getType().equals(transportType)) {
+ return record;
+ }
+ }
+ }
+ return null;
+ }
+
+ private void onTransportNotAccepted(String info){
+ if(iSdlProtocol != null) {
+ iSdlProtocol.shutdown(info);
+ }
+ }
+
+
+ public Version getProtocolVersion(){
+ return this.protocolVersion;
+ }
+
+ /**
+ * This method will set the major protocol version that we should use. It will also set the default MTU based on version.
+ * @param version major version to use
+ */
+ protected void setVersion(byte version) {
+ if (version > 5) {
+ this.protocolVersion = new Version("5.0.0"); //protect for future, proxy only supports v5 or lower
+ headerSize = V2_HEADER_SIZE;
+ mtus.put(SessionType.RPC, (long) V3_V4_MTU_SIZE);
+ } else if (version == 5) {
+ this.protocolVersion = new Version("5.0.0");
+ headerSize = V2_HEADER_SIZE;
+ mtus.put(SessionType.RPC, (long) V3_V4_MTU_SIZE);
+ }else if (version == 4) {
+ this.protocolVersion = new Version("4.0.0");
+ headerSize = V2_HEADER_SIZE;
+ mtus.put(SessionType.RPC, (long) V3_V4_MTU_SIZE); //versions 4 supports 128k MTU
+ } else if (version == 3) {
+ this.protocolVersion = new Version("3.0.0");
+ headerSize = V2_HEADER_SIZE;
+ mtus.put(SessionType.RPC, (long) V3_V4_MTU_SIZE); //versions 3 supports 128k MTU
+ } else if (version == 2) {
+ this.protocolVersion = new Version("2.0.0");
+ headerSize = V2_HEADER_SIZE;
+ mtus.put(SessionType.RPC, (long) (V1_V2_MTU_SIZE - headerSize));
+ } else if (version == 1){
+ this.protocolVersion = new Version("1.0.0");
+ headerSize = V1_HEADER_SIZE;
+ mtus.put(SessionType.RPC, (long) (V1_V2_MTU_SIZE - headerSize));
+ }
+ }
+
+ public void endSession(byte sessionID, int hashId) {
+ SdlPacket header;
+ if(protocolVersion.getMajor() < 5){
+ header = SdlPacketFactory.createEndSession(SessionType.RPC, sessionID, hashId, (byte)protocolVersion.getMajor(), BitConverter.intToByteArray(hashId));
+ }else{
+ header = SdlPacketFactory.createEndSession(SessionType.RPC, sessionID, hashId, (byte)protocolVersion.getMajor(), new byte[0]);
+ header.putTag(ControlFrameTags.RPC.EndService.HASH_ID, hashId);
+ }
+
+ handlePacketToSend(header);
+
+ } // end-method
+
+ public void sendPacket(SdlPacket packet){
+ if(transportManager != null){
+ transportManager.sendPacket(packet);
+ }
+ }
+
+ public void sendMessage(ProtocolMessage protocolMsg) {
+ protocolMsg.setRPCType((byte) 0x00); //always sending a request
+ SessionType sessionType = protocolMsg.getSessionType();
+ byte sessionID = protocolMsg.getSessionID();
+
+ byte[] data;
+ if (protocolVersion.getMajor() > 1 && sessionType != SessionType.NAV && sessionType != SessionType.PCM) {
+ if (sessionType.eq(SessionType.CONTROL)) {
+ final byte[] secureData = protocolMsg.getData().clone();
+ data = new byte[headerSize + secureData.length];
+
+ final BinaryFrameHeader binFrameHeader =
+ SdlPacketFactory.createBinaryFrameHeader(protocolMsg.getRPCType(),protocolMsg.getFunctionID(), protocolMsg.getCorrID(), 0);
+ System.arraycopy(binFrameHeader.assembleHeaderBytes(), 0, data, 0, headerSize);
+ System.arraycopy(secureData, 0, data, headerSize, secureData.length);
+ }
+ else if (protocolMsg.getBulkData() != null) {
+ data = new byte[12 + protocolMsg.getJsonSize() + protocolMsg.getBulkData().length];
+ sessionType = SessionType.BULK_DATA;
+ } else {
+ data = new byte[12 + protocolMsg.getJsonSize()];
+ }
+ if (!sessionType.eq(SessionType.CONTROL)) {
+ BinaryFrameHeader binFrameHeader = SdlPacketFactory.createBinaryFrameHeader(protocolMsg.getRPCType(), protocolMsg.getFunctionID(), protocolMsg.getCorrID(), protocolMsg.getJsonSize());
+ System.arraycopy(binFrameHeader.assembleHeaderBytes(), 0, data, 0, 12);
+ System.arraycopy(protocolMsg.getData(), 0, data, 12, protocolMsg.getJsonSize());
+ if (protocolMsg.getBulkData() != null) {
+ System.arraycopy(protocolMsg.getBulkData(), 0, data, 12 + protocolMsg.getJsonSize(), protocolMsg.getBulkData().length);
+ }
+ }
+ } else {
+ data = protocolMsg.getData();
+ }
+
+ if (iSdlProtocol != null && protocolMsg.getPayloadProtected()){
+
+ if (data != null && data.length > 0) {
+ byte[] dataToRead = new byte[TLS_MAX_RECORD_SIZE];
+ SdlSecurityBase sdlSec = iSdlProtocol.getSdlSecurity();
+ if (sdlSec == null)
+ return;
+
+ Integer iNumBytes = sdlSec.encryptData(data, dataToRead);
+ if ((iNumBytes == null) || (iNumBytes <= 0))
+ return;
+
+ byte[] encryptedData = new byte[iNumBytes];
+ System.arraycopy(dataToRead, 0, encryptedData, 0, iNumBytes);
+ data = encryptedData;
+ }
+ }
+
+ // Get the message lock for this protocol session
+ Object messageLock = _messageLocks.get(sessionID);
+ if (messageLock == null) {
+ handleProtocolError("Error sending protocol message to SDL.",
+ new SdlException("Attempt to send protocol message prior to startSession ACK.", SdlExceptionCause.SDL_UNAVAILABLE));
+ return;
+ }
+
+ synchronized(messageLock) {
+ if (data.length > getMtu(sessionType)) {
+
+ messageID++;
+
+ // Assemble first frame.
+ Long mtu = getMtu(sessionType);
+ int frameCount = Long.valueOf(data.length / mtu).intValue();
+ if (data.length % mtu > 0) {
+ frameCount++;
+ }
+ byte[] firstFrameData = new byte[8];
+ // First four bytes are data size.
+ System.arraycopy(BitConverter.intToByteArray(data.length), 0, firstFrameData, 0, 4);
+ // Second four bytes are frame count.
+ System.arraycopy(BitConverter.intToByteArray(frameCount), 0, firstFrameData, 4, 4);
+
+ SdlPacket firstHeader = SdlPacketFactory.createMultiSendDataFirst(sessionType, sessionID, messageID, (byte)protocolVersion.getMajor(),firstFrameData,protocolMsg.getPayloadProtected());
+ firstHeader.setPriorityCoefficient(1+protocolMsg.priorityCoefficient);
+ firstHeader.setTransportRecord(activeTransports.get(sessionType));
+ //Send the first frame
+ handlePacketToSend(firstHeader);
+
+ int currentOffset = 0;
+ byte frameSequenceNumber = 0;
+
+ for (int i = 0; i < frameCount; i++) {
+ if (i < (frameCount - 1)) {
+ ++frameSequenceNumber;
+ if (frameSequenceNumber ==
+ SdlPacket.FRAME_INFO_FINAL_CONNESCUTIVE_FRAME) {
+ // we can't use 0x00 as frameSequenceNumber, because
+ // it's reserved for the last frame
+ ++frameSequenceNumber;
+ }
+ } else {
+ frameSequenceNumber = SdlPacket.FRAME_INFO_FINAL_CONNESCUTIVE_FRAME;
+ } // end-if
+
+ int bytesToWrite = data.length - currentOffset;
+ if (bytesToWrite > mtu) {
+ bytesToWrite = mtu.intValue();
+ }
+ SdlPacket consecHeader = SdlPacketFactory.createMultiSendDataRest(sessionType, sessionID, bytesToWrite, frameSequenceNumber , messageID, (byte)protocolVersion.getMajor(),data, currentOffset, bytesToWrite, protocolMsg.getPayloadProtected());
+ consecHeader.setTransportRecord(activeTransports.get(sessionType));
+ consecHeader.setPriorityCoefficient(i+2+protocolMsg.priorityCoefficient);
+ handlePacketToSend(consecHeader);
+ currentOffset += bytesToWrite;
+ }
+ } else {
+ messageID++;
+ SdlPacket header = SdlPacketFactory.createSingleSendData(sessionType, sessionID, data.length, messageID, (byte)protocolVersion.getMajor(),data, protocolMsg.getPayloadProtected());
+ header.setPriorityCoefficient(protocolMsg.priorityCoefficient);
+ header.setTransportRecord(activeTransports.get(sessionType));
+ handlePacketToSend(header);
+ }
+ }
+ }
+
+ protected void handlePacketReceived(SdlPacket packet){
+ //Check for a version difference
+ if (protocolVersion == null || protocolVersion.getMajor() == 1) {
+ setVersion((byte)packet.version);
+ }
+
+ MessageFrameAssembler assembler = getFrameAssemblerForFrame(packet);
+ assembler.handleFrame(packet);
+
+ }
+
+
+ protected MessageFrameAssembler getFrameAssemblerForFrame(SdlPacket packet) {
+ Integer iSessionId = packet.getSessionId();
+ Byte bySessionId = iSessionId.byteValue();
+
+ MessageFrameAssembler ret = _assemblerForMessageID.get(packet.getMessageId());
+ if (ret == null) {
+ ret = new MessageFrameAssembler();
+ _assemblerForMessageID.put(packet.getMessageId(), ret);
+ } // end-if
+
+ return ret;
+ } // end-method
+
+
+
+ private void registerSecondaryTransport(byte sessionId, TransportRecord transportRecord) {
+ SdlPacket header = SdlPacketFactory.createRegisterSecondaryTransport(sessionId, (byte)protocolVersion.getMajor());
+ header.setTransportRecord(transportRecord);
+ handlePacketToSend(header);
+ }
+
+ public void startService(SessionType serviceType, byte sessionID, boolean isEncrypted) {
+ final SdlPacket header = SdlPacketFactory.createStartSession(serviceType, 0x00, (byte)protocolVersion.getMajor(), sessionID, isEncrypted);
+ if(SessionType.RPC.equals(serviceType)){
+ if(connectedPrimaryTransport != null) {
+ header.setTransportRecord(connectedPrimaryTransport);
+ }
+ //This is going to be our primary transport
+ header.putTag(ControlFrameTags.RPC.StartService.PROTOCOL_VERSION, MAX_PROTOCOL_VERSION.toString());
+ handlePacketToSend(header);
+ return; // We don't need to go any further
+ }else if(serviceType.equals(SessionType.NAV)){
+ if(iSdlProtocol != null){
+ VideoStreamingParameters videoStreamingParameters = iSdlProtocol.getDesiredVideoParams();
+ if(videoStreamingParameters != null) {
+ ImageResolution desiredResolution = videoStreamingParameters.getResolution();
+ VideoStreamingFormat desiredFormat = videoStreamingParameters.getFormat();
+ if (desiredResolution != null) {
+ header.putTag(ControlFrameTags.Video.StartService.WIDTH, desiredResolution.getResolutionWidth());
+ header.putTag(ControlFrameTags.Video.StartService.HEIGHT, desiredResolution.getResolutionHeight());
+ }
+ if (desiredFormat != null) {
+ header.putTag(ControlFrameTags.Video.StartService.VIDEO_CODEC, desiredFormat.getCodec().toString());
+ header.putTag(ControlFrameTags.Video.StartService.VIDEO_PROTOCOL, desiredFormat.getProtocol().toString());
+ }
+ }
+ }
+ }
+ if(transportPriorityForServiceMap == null
+ || transportPriorityForServiceMap.get(serviceType) == null
+ || transportPriorityForServiceMap.get(serviceType).isEmpty()){
+ //If there is no transport priority for this service it can be assumed it's primary
+ header.setTransportRecord(connectedPrimaryTransport);
+ handlePacketToSend(header);
+ return;
+ }
+ int transportPriority = transportPriorityForServiceMap.get(serviceType).get(0);
+ if(transportPriority == PRIMARY_TRANSPORT_ID){
+ // Primary is favored, and we're already connected...
+ header.setTransportRecord(connectedPrimaryTransport);
+ handlePacketToSend(header);
+ }else if(transportPriority == SECONDARY_TRANSPORT_ID) {
+ // Secondary is favored
+ for(TransportType secondaryTransportType : supportedSecondaryTransports) {
+
+ if(!requestedSecondaryTransports.contains(secondaryTransportType)){
+ // Secondary transport is not accepted by the client
+ continue;
+ }
+
+ if(activeTransports.get(serviceType) != null
+ && activeTransports.get(serviceType).getType() !=null
+ && activeTransports.get(serviceType).getType().equals(secondaryTransportType)){
+ // Transport is already active and accepted
+ header.setTransportRecord(activeTransports.get(serviceType));
+ handlePacketToSend(header);
+ return;
+ }
+
+
+
+ //If the secondary transport isn't connected yet that will have to be performed first
+
+ List<ISecondaryTransportListener> listenerList = secondaryTransportListeners.get(secondaryTransportType);
+ if(listenerList == null){
+ listenerList = new ArrayList<>();
+ secondaryTransportListeners.put(secondaryTransportType, listenerList);
+ }
+
+ //Check to see if the primary transport can also be used as a backup
+ final boolean primaryTransportBackup = transportPriorityForServiceMap.get(serviceType).contains(PRIMARY_TRANSPORT_ID);
+
+ ISecondaryTransportListener secondaryListener = new ISecondaryTransportListener() {
+ @Override
+ public void onConnectionSuccess(TransportRecord transportRecord) {
+ header.setTransportRecord(transportRecord);
+ handlePacketToSend(header);
+ }
+
+ @Override
+ public void onConnectionFailure() {
+ if(primaryTransportBackup) {
+ // Primary is also supported as backup
+ header.setTransportRecord(connectedPrimaryTransport);
+ handlePacketToSend(header);
+ }else{
+ Log.d(TAG, "Failed to connect secondary transport, threw away StartService");
+ }
+ }
+ };
+
+ if(transportManager.isConnected(secondaryTransportType,null)){
+ //The transport is actually connected, however no service has been registered
+ listenerList.add(secondaryListener);
+ registerSecondaryTransport(sessionID,transportManager.getTransportRecord(secondaryTransportType,null));
+ }else if(secondaryTransportParams != null && secondaryTransportParams.containsKey(secondaryTransportType)) {
+ //No acceptable secondary transport is connected, so first one must be connected
+ header.setTransportRecord(new TransportRecord(secondaryTransportType,""));
+ listenerList.add(secondaryListener);
+ transportManager.requestSecondaryTransportConnection(sessionID,secondaryTransportParams.get(secondaryTransportType));
+ }else{
+ Log.w(TAG, "No params to connect to secondary transport");
+ //Unable to register or start a secondary connection. Use the callback in case
+ //there is a chance to use the primary transport for this service.
+ secondaryListener.onConnectionFailure();
+ }
+
+ }
+ }
+ }
+
+ private void sendHeartBeatACK(SessionType sessionType, byte sessionID) {
+ final SdlPacket heartbeat = SdlPacketFactory.createHeartbeatACK(SessionType.CONTROL, sessionID, (byte)protocolVersion.getMajor());
+ heartbeat.setTransportRecord(activeTransports.get(sessionType));
+ handlePacketToSend(heartbeat);
+ }
+
+ public void endService(SessionType serviceType, byte sessionID) {
+ if(serviceType.equals(SessionType.RPC)){ //RPC session will close all other sessions so we want to make sure we use the correct EndProtocolSession method
+ endSession(sessionID,hashID);
+ }else {
+ SdlPacket header = SdlPacketFactory.createEndSession(serviceType, sessionID, hashID, (byte)protocolVersion.getMajor(), new byte[0]);
+ TransportRecord transportRecord = activeTransports.get(serviceType);
+ if(transportRecord != null){
+ header.setTransportRecord(transportRecord);
+ handlePacketToSend(header);
+ }
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ ----------------------------------- OLD ABSTRACT PROTOCOL ---------------------------------
+ -------------------------------------------------------------------------------------------*/
+
+
+ // This method is called whenever a protocol has an entire frame to send
+ /**
+ * SdlPacket should have included payload at this point.
+ * @param packet packet that will be sent to the router service
+ */
+ protected void handlePacketToSend(SdlPacket packet) {
+ synchronized(FRAME_LOCK) {
+
+ if(packet!=null){
+ iSdlProtocol.onProtocolMessageBytesToSend(packet);
+ }
+
+ }
+ }
+
+ /** This method handles the end of a protocol session. A callback is
+ * sent to the protocol listener.
+ **/
+ protected void handleServiceEndedNAK(SdlPacket packet, SessionType serviceType) {
+ if(packet.version >= 5){
+ if(DebugTool.isDebugEnabled()) {
+ //Currently this is only during a debugging session. Might pass back in the future
+ String rejectedTag = null;
+ if (serviceType.equals(SessionType.RPC)) {
+ rejectedTag = ControlFrameTags.RPC.EndServiceNAK.REJECTED_PARAMS;
+ } else if (serviceType.equals(SessionType.PCM)) {
+ rejectedTag = ControlFrameTags.Audio.EndServiceNAK.REJECTED_PARAMS;
+ } else if (serviceType.equals(SessionType.NAV)) {
+ rejectedTag = ControlFrameTags.Video.EndServiceNAK.REJECTED_PARAMS;
+ }
+
+ List<String> rejectedParams = (List<String>) packet.getTag(rejectedTag);
+ if(rejectedParams != null && rejectedParams.size() > 0){
+ StringBuilder builder = new StringBuilder();
+ builder.append("Rejected params for service type ");
+ builder.append(serviceType.getName());
+ builder.append(" :");
+ for(String rejectedParam : rejectedParams){
+ builder.append(rejectedParam);
+ builder.append(" ");
+ }
+ DebugTool.logWarning(builder.toString());
+ }
+
+ }
+ }
+
+ iSdlProtocol.onProtocolSessionEndedNACKed(serviceType, (byte)packet.getSessionId(), "");
+ }
+
+ // This method handles the end of a protocol session. A callback is
+ // sent to the protocol listener.
+ protected void handleServiceEnded(SdlPacket packet, SessionType sessionType) {
+
+ iSdlProtocol.onProtocolSessionEnded(sessionType, (byte)packet.getSessionId(), "");
+
+ }
+
+ /**
+ * This method handles the startup of a protocol session. A callback is sent
+ * to the protocol listener.
+ * @param packet StarServiceACK packet
+ * @param serviceType the service type that has just been started
+ */
+ protected void handleProtocolSessionStarted(SdlPacket packet, SessionType serviceType) {
+ // Use this sessionID to create a message lock
+ Object messageLock = _messageLocks.get((byte)packet.getSessionId());
+ if (messageLock == null) {
+ messageLock = new Object();
+ _messageLocks.put((byte)packet.getSessionId(), messageLock);
+ }
+ if(packet.version >= 5){
+ String mtuTag = null;
+ if(serviceType.equals(SessionType.RPC)){
+ mtuTag = ControlFrameTags.RPC.StartServiceACK.MTU;
+ }else if(serviceType.equals(SessionType.PCM)){
+ mtuTag = ControlFrameTags.Audio.StartServiceACK.MTU;
+ }else if(serviceType.equals(SessionType.NAV)){
+ mtuTag = ControlFrameTags.Video.StartServiceACK.MTU;
+ }
+ Object mtu = packet.getTag(mtuTag);
+ if(mtu!=null){
+ mtus.put(serviceType,(Long) packet.getTag(mtuTag));
+ }
+ if(serviceType.equals(SessionType.RPC)){
+ hashID = (Integer) packet.getTag(ControlFrameTags.RPC.StartServiceACK.HASH_ID);
+ Object version = packet.getTag(ControlFrameTags.RPC.StartServiceACK.PROTOCOL_VERSION);
+
+ if(version!=null) {
+ //At this point we have confirmed the negotiated version between the module and the proxy
+ protocolVersion = new Version((String) version);
+ }else{
+ protocolVersion = new Version("5.0.0");
+ }
+
+ //Check to make sure this is a transport we are willing to accept
+ TransportRecord transportRecord = packet.getTransportRecord();
+
+ if(transportRecord == null || !requestedPrimaryTransports.contains(transportRecord.getType())){
+ onTransportNotAccepted("Transport is not in requested primary transports");
+ return;
+ }
+
+
+ // This enables custom behavior based on protocol version specifics
+ if (protocolVersion.isNewerThan(new Version("5.1.0")) >= 0) {
+
+ if (activeTransports.get(SessionType.RPC) == null) { //Might be a better way to handle this
+
+ ArrayList<String> secondary = (ArrayList<String>) packet.getTag(ControlFrameTags.RPC.StartServiceACK.SECONDARY_TRANSPORTS);
+ ArrayList<Integer> audio = (ArrayList<Integer>) packet.getTag(ControlFrameTags.RPC.StartServiceACK.AUDIO_SERVICE_TRANSPORTS);
+ ArrayList<Integer> video = (ArrayList<Integer>) packet.getTag(ControlFrameTags.RPC.StartServiceACK.VIDEO_SERVICE_TRANSPORTS);
+
+ activeTransports.put(SessionType.RPC, transportRecord);
+ activeTransports.put(SessionType.BULK_DATA, transportRecord);
+ activeTransports.put(SessionType.CONTROL, transportRecord);
+
+ //Build out the supported secondary transports received from the
+ // RPC start service ACK.
+ supportedSecondaryTransports = new ArrayList<>();
+ if (secondary == null) {
+ // If no secondary transports were attached we should assume
+ // the Video and Audio services can be used on primary
+ if (requiresHighBandwidth
+ && TransportType.BLUETOOTH.equals(transportRecord.getType())) {
+ //transport can't support high bandwidth
+ onTransportNotAccepted(transportRecord.getType() + " can't support high bandwidth requirement, and secondary transport not supported.");
+ return;
+ }
+
+ if (video == null || video.contains(PRIMARY_TRANSPORT_ID)) {
+ activeTransports.put(SessionType.NAV, transportRecord);
+ }
+ if (audio == null || audio.contains(PRIMARY_TRANSPORT_ID)) {
+ activeTransports.put(SessionType.PCM, transportRecord);
+ }
+ }else{
+
+ if(DebugTool.isDebugEnabled()){
+ printSecondaryTransportDetails(secondary,audio,video);
+ }
+ for (String s : secondary) {
+ switch (s) {
+ case TransportConstants.TCP_WIFI:
+ supportedSecondaryTransports.add(TransportType.TCP);
+ break;
+ case TransportConstants.AOA_USB:
+ supportedSecondaryTransports.add(TransportType.USB);
+ break;
+ case TransportConstants.SPP_BLUETOOTH:
+ supportedSecondaryTransports.add(TransportType.BLUETOOTH);
+ break;
+ }
+ }
+ }
+
+ setTransportPriorityForService(SessionType.PCM, audio);
+ setTransportPriorityForService(SessionType.NAV, video);
+
+ //Update the developer on the transport status
+ notifyDevTransportListener();
+
+ } else {
+ Log.w(TAG, "Received a start service ack for RPC service while already active on a different transport.");
+ return;
+ }
+ }else {
+
+ //Version is either not included or lower than 5.1.0
+ if (requiresHighBandwidth
+ && TransportType.BLUETOOTH.equals(transportRecord.getType())) {
+ //transport can't support high bandwidth
+ onTransportNotAccepted(transportRecord.getType() + " can't support high bandwidth requirement, and secondary transport not supported in this protocol version: " + version);
+ return;
+ }
+
+ activeTransports.put(SessionType.RPC, transportRecord);
+ activeTransports.put(SessionType.BULK_DATA, transportRecord);
+ activeTransports.put(SessionType.CONTROL, transportRecord);
+ activeTransports.put(SessionType.NAV, transportRecord);
+ activeTransports.put(SessionType.PCM, transportRecord);
+
+ //Inform the developer of the initial transport connection
+ notifyDevTransportListener();
+ }
+
+
+ }else if(serviceType.equals(SessionType.NAV)){
+ if(iSdlProtocol != null) {
+ ImageResolution acceptedResolution = new ImageResolution();
+ VideoStreamingFormat acceptedFormat = new VideoStreamingFormat();
+ acceptedResolution.setResolutionHeight((Integer) packet.getTag(ControlFrameTags.Video.StartServiceACK.HEIGHT));
+ acceptedResolution.setResolutionWidth((Integer) packet.getTag(ControlFrameTags.Video.StartServiceACK.WIDTH));
+ acceptedFormat.setCodec(VideoStreamingCodec.valueForString((String) packet.getTag(ControlFrameTags.Video.StartServiceACK.VIDEO_CODEC)));
+ acceptedFormat.setProtocol(VideoStreamingProtocol.valueForString((String) packet.getTag(ControlFrameTags.Video.StartServiceACK.VIDEO_PROTOCOL)));
+ VideoStreamingParameters agreedVideoParams = iSdlProtocol.getDesiredVideoParams();
+ agreedVideoParams.setResolution(acceptedResolution);
+ agreedVideoParams.setFormat(acceptedFormat);
+ iSdlProtocol.setAcceptedVideoParams(agreedVideoParams);
+ }
+ }
+ } else {
+ TransportRecord transportRecord = packet.getTransportRecord();
+ if(transportRecord == null || (requiresHighBandwidth
+ && TransportType.BLUETOOTH.equals(transportRecord.getType()))){
+ //transport can't support high bandwidth
+ onTransportNotAccepted((transportRecord != null ? transportRecord.getType().toString() : "Transport") + "can't support high bandwidth requirement, and secondary transport not supported in this protocol version");
+ return;
+ }
+ //If version < 5 and transport is acceptable we need to just add these
+ activeTransports.put(SessionType.RPC, transportRecord);
+ activeTransports.put(SessionType.BULK_DATA, transportRecord);
+ activeTransports.put(SessionType.CONTROL, transportRecord);
+ activeTransports.put(SessionType.NAV, transportRecord);
+ activeTransports.put(SessionType.PCM, transportRecord);
+
+ if (protocolVersion.getMajor() > 1){
+ if (packet.payload!= null && packet.dataSize == 4){ //hashid will be 4 bytes in length
+ hashID = BitConverter.intFromByteArray(packet.payload, 0);
+ }
+ }
+ }
+
+ iSdlProtocol.onProtocolSessionStarted(serviceType, (byte) packet.getSessionId(), (byte)protocolVersion.getMajor(), "", hashID, packet.isEncrypted());
+ }
+
+ protected void handleProtocolSessionNAKed(SdlPacket packet, SessionType serviceType) {
+ List<String> rejectedParams = null;
+ if(packet.version >= 5){
+ if(DebugTool.isDebugEnabled()) {
+ //Currently this is only during a debugging session. Might pass back in the future
+ String rejectedTag = null;
+ if (serviceType.equals(SessionType.RPC)) {
+ rejectedTag = ControlFrameTags.RPC.StartServiceNAK.REJECTED_PARAMS;
+ } else if (serviceType.equals(SessionType.PCM)) {
+ rejectedTag = ControlFrameTags.Audio.StartServiceNAK.REJECTED_PARAMS;
+ } else if (serviceType.equals(SessionType.NAV)) {
+ rejectedTag = ControlFrameTags.Video.StartServiceNAK.REJECTED_PARAMS;
+ }
+
+ rejectedParams = (List<String>) packet.getTag(rejectedTag);
+ if(rejectedParams != null && rejectedParams.size() > 0){
+ StringBuilder builder = new StringBuilder();
+ builder.append("Rejected params for service type ");
+ builder.append(serviceType.getName());
+ builder.append(" :");
+ for(String rejectedParam : rejectedParams){
+ builder.append(rejectedParam);
+ builder.append(" ");
+ }
+ DebugTool.logWarning(builder.toString());
+ }
+
+ }
+ }
+ if (serviceType.eq(SessionType.NAV) || serviceType.eq(SessionType.PCM)) {
+ iSdlProtocol.onProtocolSessionNACKed(serviceType, (byte)packet.sessionId, (byte)protocolVersion.getMajor(), "", rejectedParams);
+
+ } else {
+ handleProtocolError("Got StartSessionNACK for protocol sessionID = " + packet.getSessionId(), null);
+ }
+ }
+
+ // This method handles protocol errors. A callback is sent to the protocol
+ // listener.
+ protected void handleProtocolError(String string, Exception ex) {
+ iSdlProtocol.onProtocolError(string, ex);
+ }
+
+ protected void handleProtocolHeartbeat(SessionType sessionType, byte sessionID) {
+ sendHeartBeatACK(sessionType,sessionID);
+ }
+
+ protected void handleServiceDataACK(SdlPacket packet, SessionType sessionType) {
+
+ if (packet.getPayload() != null && packet.getDataSize() == 4){ //service data ack will be 4 bytes in length
+ int serviceDataAckSize = BitConverter.intFromByteArray(packet.getPayload(), 0);
+ iSdlProtocol.onProtocolServiceDataACK(sessionType, serviceDataAckSize, (byte)packet.getSessionId ());
+
+ }
+ }
+
+ /* --------------------------------------------------------------------------------------------
+ ----------------------------------- TRANSPORT_TYPE LISTENER ---------------------------------
+ -------------------------------------------------------------------------------------------*/
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private final TransportManager.TransportEventListener transportEventListener = new TransportManager.TransportEventListener() {
+ private boolean requestedSession = false;
+
+ @Override
+ public void onPacketReceived(SdlPacket packet) {
+ handlePacketReceived(packet);
+ }
+
+ @Override
+ public void onTransportConnected(List<TransportRecord> connectedTransports) {
+ Log.d(TAG, "onTransportConnected");
+ //In the future we should move this logic into the Protocol Layer
+ TransportRecord transportRecord = getTransportForSession(SessionType.RPC);
+ if(transportRecord == null && !requestedSession){ //There is currently no transport registered
+ requestedSession = true;
+ transportManager.requestNewSession(getPreferredTransport(requestedPrimaryTransports,connectedTransports));
+ }
+ onTransportsConnectedUpdate(connectedTransports);
+ if(DebugTool.isDebugEnabled()){
+ printActiveTransports();
+ }
+ }
+
+ @Override
+ public void onTransportDisconnected(String info, TransportRecord disconnectedTransport, List<TransportRecord> connectedTransports) {
+ if (disconnectedTransport == null) {
+ Log.d(TAG, "onTransportDisconnected");
+ transportManager.close(iSdlProtocol.getSessionId());
+ iSdlProtocol.shutdown("No transports left connected");
+ return;
+ } else {
+ Log.d(TAG, "onTransportDisconnected - " + disconnectedTransport.getType().name());
+ }
+
+ //In the future we will actually compare the record but at this point we can assume only
+ //a single transport record per transport.
+ //TransportType type = disconnectedTransport.getType();
+ if(disconnectedTransport.equals(getTransportForSession(SessionType.NAV))){
+ //stopVideoStream();
+ iSdlProtocol.stopStream(SessionType.NAV);
+ activeTransports.remove(SessionType.NAV);
+ }
+ if(disconnectedTransport.equals(getTransportForSession(SessionType.PCM))){
+ //stopAudioStream();
+ iSdlProtocol.stopStream(SessionType.PCM);
+ activeTransports.remove(SessionType.PCM);
+ }
+
+ if(disconnectedTransport.equals(getTransportForSession(SessionType.RPC))){
+ //transportTypes.remove(type);
+ boolean primaryTransportAvailable = false;
+ if(requestedPrimaryTransports != null && requestedPrimaryTransports.size() > 1){
+ for (TransportType transportType: requestedPrimaryTransports){ Log.d(TAG, "Checking " + transportType.name());
+ if(!disconnectedTransport.getType().equals(transportType)
+ && transportManager != null
+ && transportManager.isConnected(transportType,null)){
+ primaryTransportAvailable = true;
+ //( transportConfig).setService(transportManager.getRouterService());
+ break;
+ }
+ }
+ }
+ transportManager.close(iSdlProtocol.getSessionId());
+ transportManager = null;
+ requestedSession = false;
+
+ activeTransports.clear();
+
+ iSdlProtocol.onTransportDisconnected(info, primaryTransportAvailable, transportConfig);
+
+ } //else Transport was not primary, continuing to stay connected
+
+ //Update the developer since a transport just disconnected
+ notifyDevTransportListener();
+
+ }
+
+ @Override
+ public void onError(String info) {
+ iSdlProtocol.shutdown(info);
+
+ }
+
+ @Override
+ public boolean onLegacyModeEnabled(String info) {
+ //Await a connection from the legacy transport
+ if(requestedPrimaryTransports!= null && requestedPrimaryTransports.contains(TransportType.BLUETOOTH)
+ ){//FIXME && !transportConfig.requiresHighBandwidth()){
+ Log.d(TAG, "Entering legacy mode; creating new protocol instance");
+ reset();
+ return true;
+ }else{
+ Log.d(TAG, "Bluetooth is not an acceptable transport; not moving to legacy mode");
+ return false;
+ }
+ }
+ };
+
+/* -------------------------------------------------------------------------------------------------
+----------------------------------- Internal Classes ------------------------------------------
+--------------------------------------------------------------------------------------------------*/
+
+
+ protected class MessageFrameAssembler {
+ protected ByteArrayOutputStream accumulator = null;
+ protected int totalSize = 0;
+
+ protected void handleFirstDataFrame(SdlPacket packet) {
+ //The message is new, so let's figure out how big it is.
+ totalSize = BitConverter.intFromByteArray(packet.payload, 0) - headerSize;
+ try {
+ accumulator = new ByteArrayOutputStream(totalSize);
+ }catch(OutOfMemoryError e){
+ DebugTool.logError("OutOfMemory error", e); //Garbled bits were received
+ accumulator = null;
+ }
+ }
+
+ protected void handleRemainingFrame(SdlPacket packet) {
+ accumulator.write(packet.payload, 0, (int)packet.getDataSize());
+ notifyIfFinished(packet);
+ }
+
+ protected void notifyIfFinished(SdlPacket packet) {
+ if (packet.getFrameType() == FrameType.Consecutive && packet.getFrameInfo() == 0x0) {
+ ProtocolMessage message = new ProtocolMessage();
+ message.setPayloadProtected(packet.isEncrypted());
+ message.setSessionType(SessionType.valueOf((byte)packet.getServiceType()));
+ message.setSessionID((byte)packet.getSessionId());
+ //If it is WiPro 2.0 it must have binary header
+ if (protocolVersion.getMajor() > 1) {
+ BinaryFrameHeader binFrameHeader = BinaryFrameHeader.
+ parseBinaryHeader(accumulator.toByteArray());
+ if(binFrameHeader == null) {
+ return;
+ }
+ message.setVersion((byte)protocolVersion.getMajor());
+ message.setRPCType(binFrameHeader.getRPCType());
+ message.setFunctionID(binFrameHeader.getFunctionID());
+ message.setCorrID(binFrameHeader.getCorrID());
+ if (binFrameHeader.getJsonSize() > 0) message.setData(binFrameHeader.getJsonData());
+ if (binFrameHeader.getBulkData() != null) message.setBulkData(binFrameHeader.getBulkData());
+ } else{
+ message.setData(accumulator.toByteArray());
+ }
+
+ _assemblerForMessageID.remove(packet.getMessageId());
+
+ try {
+ iSdlProtocol.onProtocolMessageReceived(message);
+ } catch (Exception excp) {
+ DebugTool.logError(FailurePropagating_Msg + "onProtocolMessageReceived: " + excp.toString(), excp);
+ } // end-catch
+
+ accumulator = null;
+ } // end-if
+ } // end-method
+
+ protected void handleMultiFrameMessageFrame(SdlPacket packet) {
+ if (packet.getFrameType() == FrameType.First) {
+ handleFirstDataFrame(packet);
+ }
+ else{
+ if(accumulator != null){
+ handleRemainingFrame(packet);
+ }
+ }
+
+ } // end-method
+
+ protected void handleFrame(SdlPacket packet) {
+
+ if (packet.getPayload() != null && packet.getDataSize() > 0 && packet.isEncrypted() ) {
+
+ SdlSecurityBase sdlSec = iSdlProtocol.getSdlSecurity();
+ byte[] dataToRead = new byte[4096];
+
+ Integer iNumBytes = sdlSec.decryptData(packet.getPayload(), dataToRead);
+ if ((iNumBytes == null) || (iNumBytes <= 0)){
+ return;
+ }
+
+ byte[] decryptedData = new byte[iNumBytes];
+ System.arraycopy(dataToRead, 0, decryptedData, 0, iNumBytes);
+ packet.payload = decryptedData;
+ }
+
+ if (packet.getFrameType().equals(FrameType.Control)) {
+ handleControlFrame(packet);
+ } else {
+ // Must be a form of data frame (single, first, consecutive, etc.)
+ if ( packet.getFrameType() == FrameType.First
+ || packet.getFrameType() == FrameType.Consecutive
+ ) {
+ handleMultiFrameMessageFrame(packet);
+ } else {
+ handleSingleFrameMessageFrame(packet);
+ }
+ }
+ }
+
+ private void handleProtocolHeartbeatACK(SdlPacket packet) {
+ //Heartbeat is not supported in the SdlProtocol class beyond responding with ACKs to
+ //heartbeat messages. Receiving this ACK is suspicious and should be logged
+ DebugTool.logInfo("Received HeartbeatACK - " + packet.toString());
+ }
+
+ private void handleProtocolHeartbeat(SdlPacket packet) {
+ SdlProtocol.this.handleProtocolHeartbeat(SessionType.valueOf((byte)packet.getServiceType()),(byte)packet.getSessionId());
+ }
+
+ /**
+ * Directing method that will push the packet to the method that can handle it best
+ * @param packet a control frame packet
+ */
+ private void handleControlFrame(SdlPacket packet) {
+ Integer frameTemp = packet.getFrameInfo();
+ Byte frameInfo = frameTemp.byteValue();
+
+ SessionType serviceType = SessionType.valueOf((byte)packet.getServiceType());
+
+ if (frameInfo == FrameDataControlFrameType.Heartbeat.getValue()) {
+
+ handleProtocolHeartbeat(packet);
+
+ }else if (frameInfo == FrameDataControlFrameType.HeartbeatACK.getValue()) {
+
+ handleProtocolHeartbeatACK(packet);
+
+ }else if (frameInfo == FrameDataControlFrameType.StartSessionACK.getValue()) {
+
+ handleProtocolSessionStarted(packet, serviceType);
+
+ } else if (frameInfo == FrameDataControlFrameType.StartSessionNACK.getValue()) {
+
+ handleProtocolSessionNAKed(packet, serviceType);
+
+ } else if (frameInfo == FrameDataControlFrameType.EndSession.getValue()
+ || frameInfo == FrameDataControlFrameType.EndSessionACK.getValue()) {
+
+ handleServiceEnded(packet,serviceType);
+
+ } else if (frameInfo == FrameDataControlFrameType.EndSessionNACK.getValue()) {
+
+ handleServiceEndedNAK(packet, serviceType);
+
+ } else if (frameInfo == FrameDataControlFrameType.ServiceDataACK.getValue()) {
+
+ handleServiceDataACK(packet, serviceType);
+
+ } else if (frameInfo == FrameDataControlFrameType.RegisterSecondaryTransportACK.getValue()) {
+
+ handleSecondaryTransportRegistration(packet.getTransportRecord(),true);
+
+ } else if (frameInfo == FrameDataControlFrameType.RegisterSecondaryTransportNACK.getValue()) {
+
+ String reason = (String) packet.getTag(ControlFrameTags.RPC.RegisterSecondaryTransportNAK.REASON);
+ DebugTool.logWarning(reason);
+ handleSecondaryTransportRegistration(packet.getTransportRecord(),false);
+
+ } else if (frameInfo == FrameDataControlFrameType.TransportEventUpdate.getValue()) {
+
+ // Get TCP params
+ String ipAddr = (String) packet.getTag(ControlFrameTags.RPC.TransportEventUpdate.TCP_IP_ADDRESS);
+ Integer port = (Integer) packet.getTag(ControlFrameTags.RPC.TransportEventUpdate.TCP_PORT);
+
+ if(secondaryTransportParams == null){
+ secondaryTransportParams = new HashMap<>();
+ }
+
+ if(ipAddr != null && port != null) {
+ //FIXME
+ /* Bundle bundle = new Bundle();
+ bundle.putString(ControlFrameTags.RPC.TransportEventUpdate.TCP_IP_ADDRESS, ipAddr);
+ bundle.putInt(ControlFrameTags.RPC.TransportEventUpdate.TCP_PORT, port);
+ bundle.putString(TransportConstants.TRANSPORT_TYPE, TransportType.TCP.name());
+ secondaryTransportParams.put(TransportType.TCP, bundle);*/
+
+ //A new secondary transport just became available. Notify the developer.
+ notifyDevTransportListener();
+ }
+
+ }
+
+ _assemblerForMessageID.remove(packet.getMessageId());
+
+ } // end-method
+
+ private void handleSingleFrameMessageFrame(SdlPacket packet) {
+ ProtocolMessage message = new ProtocolMessage();
+ message.setPayloadProtected(packet.isEncrypted());
+ SessionType serviceType = SessionType.valueOf((byte)packet.getServiceType());
+ if (serviceType == SessionType.RPC) {
+ message.setMessageType(MessageType.RPC);
+ } else if (serviceType == SessionType.BULK_DATA) {
+ message.setMessageType(MessageType.BULK);
+ } // end-if
+ message.setSessionType(serviceType);
+ message.setSessionID((byte)packet.getSessionId());
+ //If it is WiPro 2.0 it must have binary header
+ boolean isControlService = message.getSessionType().equals(SessionType.CONTROL);
+ if (protocolVersion.getMajor() > 1 && !isControlService) {
+ BinaryFrameHeader binFrameHeader = BinaryFrameHeader.
+ parseBinaryHeader(packet.payload);
+ if(binFrameHeader == null) {
+ return;
+ }
+ message.setVersion((byte)protocolVersion.getMajor());
+ message.setRPCType(binFrameHeader.getRPCType());
+ message.setFunctionID(binFrameHeader.getFunctionID());
+ message.setCorrID(binFrameHeader.getCorrID());
+ if (binFrameHeader.getJsonSize() > 0){
+ message.setData(binFrameHeader.getJsonData());
+ }
+ if (binFrameHeader.getBulkData() != null){
+ message.setBulkData(binFrameHeader.getBulkData());
+ }
+ } else {
+ message.setData(packet.payload);
+ }
+
+ _assemblerForMessageID.remove(packet.getMessageId());
+
+ try {
+ iSdlProtocol.onProtocolMessageReceived(message);
+ } catch (Exception ex) {
+ DebugTool.logError(FailurePropagating_Msg + "onProtocolMessageReceived: " + ex.toString(), ex);
+ handleProtocolError(FailurePropagating_Msg + "onProtocolMessageReceived: ", ex);
+ } // end-catch
+ } // end-method
+ } // end-class
+
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/IProxyListener.java b/base/src/main/java/com/smartdevicelink/proxy/IProxyListener.java
new file mode 100644
index 000000000..665a2c9e8
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/IProxyListener.java
@@ -0,0 +1,19 @@
+package com.smartdevicelink.proxy;
+
+import com.smartdevicelink.proxy.interfaces.IProxyListenerBase;
+import com.smartdevicelink.proxy.rpc.OnAppInterfaceUnregistered;
+import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse;
+import com.smartdevicelink.proxy.rpc.UnregisterAppInterfaceResponse;
+
+public interface IProxyListener extends IProxyListenerBase{
+ // Adds Legacy Life-cycle Management call-backs to the IProxyListenerAbstract interface
+
+ public void onProxyOpened();
+
+ public void onRegisterAppInterfaceResponse(RegisterAppInterfaceResponse response);
+
+ public void onOnAppInterfaceUnregistered(OnAppInterfaceUnregistered notification);
+
+ public void onUnregisterAppInterfaceResponse(UnregisterAppInterfaceResponse response);
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCMessage.java b/base/src/main/java/com/smartdevicelink/proxy/RPCMessage.java
new file mode 100644
index 000000000..8135424fc
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCMessage.java
@@ -0,0 +1,138 @@
+package com.smartdevicelink.proxy;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+
+import java.util.Hashtable;
+
+public class RPCMessage extends RPCStruct {
+ public static final String KEY_REQUEST = "request";
+ public static final String KEY_RESPONSE = "response";
+ public static final String KEY_NOTIFICATION = "notification";
+ public static final String KEY_FUNCTION_NAME = "name";
+ public static final String KEY_PARAMETERS = "parameters";
+ public static final String KEY_CORRELATION_ID = "correlationID";
+
+ public RPCMessage(String functionName) {
+ this(functionName, "request");
+ }
+
+ protected RPCMessage(RPCMessage rpcm) {
+ this(rpcm.store);
+ }
+
+ protected RPCMessage(RPCStruct rpcs) {
+ this("", "");
+ this.parameters = rpcs.store;
+ }
+
+ public RPCMessage(String functionName, String messageType) {
+ function = new Hashtable<String, Object>();
+ parameters = new Hashtable<String, Object>();
+
+ this.messageType = messageType;
+ function.put(KEY_PARAMETERS, parameters);
+
+ if (messageType != null)
+ store.put(messageType, function);
+ if (functionName != null)
+ function.put(KEY_FUNCTION_NAME, functionName);
+ }
+
+ @SuppressWarnings("unchecked")
+ public RPCMessage(Hashtable<String, Object> hash) {
+ store = hash;
+ messageType = getMessageTypeName(hash.keySet());
+ function = (Hashtable<String, Object>) hash.get(messageType);
+ parameters = (Hashtable<String, Object>) function.get(KEY_PARAMETERS);
+ if (hasKey(hash.keySet(), RPCStruct.KEY_BULK_DATA)) {
+ setBulkData((byte[]) hash.get(RPCStruct.KEY_BULK_DATA));
+ }
+ if (hasKey(hash.keySet(), RPCStruct.KEY_PROTECTED)) {
+ setPayloadProtected((Boolean) hash.get(RPCStruct.KEY_PROTECTED));
+ }
+ }
+
+ public FunctionID getFunctionID(){
+ if(function.containsKey(KEY_FUNCTION_NAME)){
+ return FunctionID.getEnumForString((String)function.get(KEY_FUNCTION_NAME));
+ }
+ return null;
+ }
+
+ protected String messageType;
+ protected Hashtable<String, Object> parameters;
+ protected Hashtable<String, Object> function;
+
+ public String getFunctionName() {
+ return (String)function.get(KEY_FUNCTION_NAME);
+ }
+
+ protected void setFunctionName(String functionName) {
+ function.put(KEY_FUNCTION_NAME, functionName);
+ }
+
+ public String getMessageType() {
+ if (messageType.equals(KEY_REQUEST) ||
+ messageType.equals(KEY_RESPONSE) ||
+ messageType.equals(KEY_NOTIFICATION)) {
+ return messageType;
+ }
+ return null;
+ }
+
+ // Generalized Getters and Setters
+
+ public void setParameters(String key, Object value) {
+ if (value != null) {
+ parameters.put(key, value);
+ } else {
+ parameters.remove(key);
+ }
+ }
+
+ public Object getParameters(String key) {
+ return parameters.get(key);
+ }
+
+ @Override
+ public Object getObject(Class tClass, String key) {
+ Object obj = parameters.get(key);
+ return formatObject(tClass, obj);
+ }
+
+ // Common Object Getters
+
+ @Override
+ public String getString(String key) {
+ return (String) parameters.get(key);
+ }
+
+ @Override
+ public Integer getInteger(String key) {
+ return (Integer) parameters.get(key);
+ }
+
+ @Override
+ public Float getFloat(String key) {
+ return (Float) parameters.get(key);
+ }
+
+ @Override
+ public Double getDouble(String key) {
+ return (Double) parameters.get(key);
+ }
+
+ @Override
+ public Boolean getBoolean(String key) { return (Boolean) parameters.get(key); }
+
+ @Override
+ public Long getLong(String key){
+ Object result = parameters.get(key);
+ if (result instanceof Integer) {
+ return ((Integer) result).longValue();
+ }else if(result instanceof Long){
+ return (Long) result;
+ }
+ return null;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCNotification.java b/base/src/main/java/com/smartdevicelink/proxy/RPCNotification.java
new file mode 100644
index 000000000..fdb7761df
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCNotification.java
@@ -0,0 +1,29 @@
+/**
+ *
+ */
+package com.smartdevicelink.proxy;
+
+import java.util.Hashtable;
+
+public class RPCNotification extends RPCMessage {
+
+ public RPCNotification(String functionName) {
+ super(functionName, RPCMessage.KEY_NOTIFICATION);
+ }
+
+ public RPCNotification(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+
+ public RPCNotification(RPCMessage rpcMsg) {
+ super(preprocessMsg(rpcMsg));
+ }
+
+ static RPCMessage preprocessMsg (RPCMessage rpcMsg) {
+ if (rpcMsg.getMessageType() != RPCMessage.KEY_NOTIFICATION) {
+ rpcMsg.messageType = RPCMessage.KEY_NOTIFICATION;
+ }
+
+ return rpcMsg;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCRequest.java b/base/src/main/java/com/smartdevicelink/proxy/RPCRequest.java
new file mode 100644
index 000000000..fa4700a81
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCRequest.java
@@ -0,0 +1,50 @@
+/**
+ *
+ */
+package com.smartdevicelink.proxy;
+
+import java.util.Hashtable;
+
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.CorrelationIdGenerator;
+
+public class RPCRequest extends RPCMessage {
+
+ protected OnRPCResponseListener onResponseListener;
+
+ public RPCRequest(String functionName) {
+ super(functionName, RPCMessage.KEY_REQUEST);
+ messageType = RPCMessage.KEY_REQUEST;
+ }
+
+ public RPCRequest(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+
+ public RPCRequest(RPCRequest request){
+ super(request);
+ setCorrelationID(CorrelationIdGenerator.generateId());
+ }
+ public Integer getCorrelationID() {
+ //First we check to see if a correlation ID is set. If not, create one.
+ if(!function.containsKey(RPCMessage.KEY_CORRELATION_ID)){
+ setCorrelationID(CorrelationIdGenerator.generateId());
+ }
+ return (Integer)function.get(RPCMessage.KEY_CORRELATION_ID);
+ }
+
+ public void setCorrelationID(Integer correlationID) {
+ if (correlationID != null) {
+ function.put(RPCMessage.KEY_CORRELATION_ID, correlationID );
+ } else {
+ function.remove(RPCMessage.KEY_CORRELATION_ID);
+ }
+ }
+ public void setOnRPCResponseListener(OnRPCResponseListener listener){
+ onResponseListener = listener;
+ }
+
+ public OnRPCResponseListener getOnRPCResponseListener(){
+ return this.onResponseListener;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java b/base/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java
new file mode 100644
index 000000000..349f9783f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCRequestFactory.java
@@ -0,0 +1,997 @@
+package com.smartdevicelink.proxy;
+
+import java.util.Vector;
+
+import com.smartdevicelink.proxy.rpc.AddCommand;
+import com.smartdevicelink.proxy.rpc.AddSubMenu;
+import com.smartdevicelink.proxy.rpc.Alert;
+import com.smartdevicelink.proxy.rpc.ChangeRegistration;
+import com.smartdevicelink.proxy.rpc.Choice;
+import com.smartdevicelink.proxy.rpc.CreateInteractionChoiceSet;
+import com.smartdevicelink.proxy.rpc.DeleteCommand;
+import com.smartdevicelink.proxy.rpc.DeleteFile;
+import com.smartdevicelink.proxy.rpc.DeleteInteractionChoiceSet;
+import com.smartdevicelink.proxy.rpc.DeleteSubMenu;
+import com.smartdevicelink.proxy.rpc.DeviceInfo;
+import com.smartdevicelink.proxy.rpc.EndAudioPassThru;
+import com.smartdevicelink.proxy.rpc.GetVehicleData;
+import com.smartdevicelink.proxy.rpc.Image;
+import com.smartdevicelink.proxy.rpc.ListFiles;
+import com.smartdevicelink.proxy.rpc.MenuParams;
+import com.smartdevicelink.proxy.rpc.PerformAudioPassThru;
+import com.smartdevicelink.proxy.rpc.PerformInteraction;
+import com.smartdevicelink.proxy.rpc.PutFile;
+import com.smartdevicelink.proxy.rpc.RegisterAppInterface;
+import com.smartdevicelink.proxy.rpc.ScrollableMessage;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.SetAppIcon;
+import com.smartdevicelink.proxy.rpc.SetDisplayLayout;
+import com.smartdevicelink.proxy.rpc.SetGlobalProperties;
+import com.smartdevicelink.proxy.rpc.SetMediaClockTimer;
+import com.smartdevicelink.proxy.rpc.Show;
+import com.smartdevicelink.proxy.rpc.Slider;
+import com.smartdevicelink.proxy.rpc.SoftButton;
+import com.smartdevicelink.proxy.rpc.Speak;
+import com.smartdevicelink.proxy.rpc.StartTime;
+import com.smartdevicelink.proxy.rpc.SubscribeButton;
+import com.smartdevicelink.proxy.rpc.SubscribeVehicleData;
+import com.smartdevicelink.proxy.rpc.SystemRequest;
+import com.smartdevicelink.proxy.rpc.TTSChunk;
+import com.smartdevicelink.proxy.rpc.UnregisterAppInterface;
+import com.smartdevicelink.proxy.rpc.UnsubscribeButton;
+import com.smartdevicelink.proxy.rpc.UnsubscribeVehicleData;
+import com.smartdevicelink.proxy.rpc.VrHelpItem;
+import com.smartdevicelink.proxy.rpc.enums.AppHMIType;
+import com.smartdevicelink.proxy.rpc.enums.AudioType;
+import com.smartdevicelink.proxy.rpc.enums.BitsPerSample;
+import com.smartdevicelink.proxy.rpc.enums.ButtonName;
+import com.smartdevicelink.proxy.rpc.enums.FileType;
+import com.smartdevicelink.proxy.rpc.enums.ImageType;
+import com.smartdevicelink.proxy.rpc.enums.InteractionMode;
+import com.smartdevicelink.proxy.rpc.enums.Language;
+import com.smartdevicelink.proxy.rpc.enums.RequestType;
+import com.smartdevicelink.proxy.rpc.enums.SamplingRate;
+import com.smartdevicelink.proxy.rpc.enums.TextAlignment;
+import com.smartdevicelink.proxy.rpc.enums.UpdateMode;
+
+/**
+ * @deprecated Use RPC constructors directly instead
+ */
+@Deprecated
+public class RPCRequestFactory {
+
+ public static final int SDL_MSG_MAJOR_VERSION = 1;
+ public static final int SDL_MSG_MINOR_VERSION = 0;
+
+ public static SystemRequest buildSystemRequest(
+ String data, Integer correlationID) {
+
+ if(data == null) return null;
+
+ SystemRequest msg = new SystemRequest();
+ msg.setRequestType(RequestType.PROPRIETARY);
+ msg.setCorrelationID(correlationID);
+ msg.setBulkData(data.getBytes());
+ return msg;
+ }
+
+ public static SystemRequest buildSystemRequestLegacy(
+ Vector<String> data, Integer correlationID) {
+
+ if(data == null) return null;
+
+ SystemRequest msg = new SystemRequest(true);
+ msg.setCorrelationID(correlationID);
+ msg.setLegacyData(data);
+ return msg;
+ }
+
+ public static AddCommand buildAddCommand(Integer commandID,
+ String menuText, Integer parentID, Integer position,
+ Vector<String> vrCommands, Image cmdIcon, Integer correlationID) {
+ AddCommand msg = new AddCommand();
+ msg.setCorrelationID(correlationID);
+ msg.setCmdID(commandID);
+ msg.setVrCommands(vrCommands);
+
+ if (cmdIcon != null) msg.setCmdIcon(cmdIcon);
+
+ if(menuText != null || parentID != null || position != null) {
+ MenuParams menuParams = new MenuParams();
+ menuParams.setMenuName(menuText);
+ menuParams.setPosition(position);
+ menuParams.setParentID(parentID);
+ msg.setMenuParams(menuParams);
+ }
+
+ return msg;
+ }
+
+ public static AddCommand buildAddCommand(Integer commandID,
+ String menuText, Integer parentID, Integer position,
+ Vector<String> vrCommands, String IconValue, ImageType IconType,
+ Integer correlationID) {
+ AddCommand msg = new AddCommand();
+ msg.setCorrelationID(correlationID);
+ msg.setCmdID(commandID);
+
+ if (vrCommands != null) msg.setVrCommands(vrCommands);
+
+ Image cmdIcon = null;
+
+ if (IconValue != null && IconType != null)
+ {
+ cmdIcon = new Image();
+ cmdIcon.setValue(IconValue);
+ cmdIcon.setImageType(IconType);
+ }
+
+ if (cmdIcon != null) msg.setCmdIcon(cmdIcon);
+
+ if(menuText != null || parentID != null || position != null) {
+ MenuParams menuParams = new MenuParams();
+ menuParams.setMenuName(menuText);
+ menuParams.setPosition(position);
+ menuParams.setParentID(parentID);
+ msg.setMenuParams(menuParams);
+ }
+
+ return msg;
+ }
+
+ public static AddCommand buildAddCommand(Integer commandID,
+ String menuText, Integer parentID, Integer position,
+ Vector<String> vrCommands, Integer correlationID) {
+ AddCommand msg = new AddCommand();
+ msg.setCorrelationID(correlationID);
+ msg.setCmdID(commandID);
+ msg.setVrCommands(vrCommands);
+
+ if(menuText != null || parentID != null || position != null) {
+ MenuParams menuParams = new MenuParams();
+ menuParams.setMenuName(menuText);
+ menuParams.setPosition(position);
+ menuParams.setParentID(parentID);
+ msg.setMenuParams(menuParams);
+ }
+
+ return msg;
+ }
+
+ public static AddCommand buildAddCommand(Integer commandID,
+ String menuText, Vector<String> vrCommands, Integer correlationID) {
+ AddCommand msg = buildAddCommand(commandID, menuText, null, null,
+ vrCommands, correlationID);
+ return msg;
+ }
+
+ public static AddCommand buildAddCommand(Integer commandID,
+ Vector<String> vrCommands, Integer correlationID) {
+ AddCommand msg = new AddCommand();
+ msg.setCorrelationID(correlationID);
+ msg.setCmdID(commandID);
+ msg.setVrCommands(vrCommands);
+
+ return msg;
+ }
+
+ public static AddSubMenu buildAddSubMenu(Integer menuID, String menuName,
+ Integer correlationID) {
+ AddSubMenu msg = buildAddSubMenu(menuID, menuName, null, correlationID);
+ return msg;
+ }
+
+ public static AddSubMenu buildAddSubMenu(Integer menuID, String menuName,
+ Integer position, Integer correlationID) {
+ AddSubMenu msg = new AddSubMenu();
+ msg.setCorrelationID(correlationID);
+ msg.setMenuName(menuName);
+ msg.setMenuID(menuID);
+ msg.setPosition(position);
+
+ return msg;
+ }
+
+ public static Alert buildAlert(String ttsText, Boolean playTone,
+ Vector<SoftButton> softButtons, Integer correlationID) {
+ Vector<TTSChunk> chunks = TTSChunkFactory
+ .createSimpleTTSChunks(ttsText);
+ Alert msg = buildAlert(chunks, null, null, null, playTone, null, softButtons,
+ correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(String alertText1, String alertText2,
+ String alertText3, Integer duration, Vector<SoftButton> softButtons,
+ Integer correlationID) {
+ Alert msg = buildAlert((Vector<TTSChunk>) null, alertText1, alertText2,
+ alertText3, null, duration, softButtons, correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(String ttsText, String alertText1,
+ String alertText2, String alertText3, Boolean playTone,
+ Integer duration, Vector<SoftButton> softButtons, Integer correlationID) {
+ Vector<TTSChunk> chunks = TTSChunkFactory
+ .createSimpleTTSChunks(ttsText);
+ Alert msg = buildAlert(chunks, alertText1, alertText2, alertText3,
+ playTone, duration, softButtons, correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(Vector<TTSChunk> chunks, Boolean playTone,
+ Vector<SoftButton> softButtons, Integer correlationID) {
+ Alert msg = buildAlert(chunks, null, null, null, playTone, null, softButtons, correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(Vector<TTSChunk> ttsChunks,
+ String alertText1, String alertText2, String alertText3,
+ Boolean playTone, Integer duration, Vector<SoftButton> softButtons,
+ Integer correlationID) {
+ Alert msg = new Alert();
+ msg.setCorrelationID(correlationID);
+ msg.setAlertText1(alertText1);
+ msg.setAlertText2(alertText2);
+ msg.setAlertText3(alertText3);
+ msg.setDuration(duration);
+ msg.setPlayTone(playTone);
+ msg.setTtsChunks(ttsChunks);
+ msg.setSoftButtons(softButtons);
+
+ return msg;
+ }
+
+ public static Alert buildAlert(String ttsText, Boolean playTone,
+ Integer correlationID) {
+ Vector<TTSChunk> chunks = TTSChunkFactory
+ .createSimpleTTSChunks(ttsText);
+ Alert msg = buildAlert(chunks, null, null, playTone, null,
+ correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(String alertText1, String alertText2,
+ Integer duration, Integer correlationID) {
+ Alert msg = buildAlert((Vector<TTSChunk>) null, alertText1, alertText2,
+ null, duration, correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(String ttsText, String alertText1,
+ String alertText2, Boolean playTone, Integer duration,
+ Integer correlationID) {
+ Vector<TTSChunk> chunks = TTSChunkFactory
+ .createSimpleTTSChunks(ttsText);
+ Alert msg = buildAlert(chunks, alertText1, alertText2, playTone,
+ duration, correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(Vector<TTSChunk> chunks, Boolean playTone,
+ Integer correlationID) {
+ Alert msg = buildAlert(chunks, null, null, playTone, null,
+ correlationID);
+ return msg;
+ }
+
+ public static Alert buildAlert(Vector<TTSChunk> ttsChunks,
+ String alertText1, String alertText2, Boolean playTone,
+ Integer duration, Integer correlationID) {
+ Alert msg = new Alert();
+ msg.setCorrelationID(correlationID);
+ msg.setAlertText1(alertText1);
+ msg.setAlertText2(alertText2);
+ msg.setDuration(duration);
+ msg.setPlayTone(playTone);
+ msg.setTtsChunks(ttsChunks);
+
+ return msg;
+ }
+
+ public static CreateInteractionChoiceSet buildCreateInteractionChoiceSet(
+ Vector<Choice> choiceSet, Integer interactionChoiceSetID,
+ Integer correlationID) {
+ CreateInteractionChoiceSet msg = new CreateInteractionChoiceSet();
+ msg.setChoiceSet(choiceSet);
+ msg.setInteractionChoiceSetID(interactionChoiceSetID);
+ msg.setCorrelationID(correlationID);
+ return msg;
+ }
+
+ public static DeleteCommand buildDeleteCommand(Integer commandID,
+ Integer correlationID) {
+ DeleteCommand msg = new DeleteCommand();
+ msg.setCmdID(commandID);
+ msg.setCorrelationID(correlationID);
+ return msg;
+ }
+
+ public static DeleteFile buildDeleteFile(String sdlFileName,
+ Integer correlationID) {
+ DeleteFile deleteFile = new DeleteFile();
+ deleteFile.setCorrelationID(correlationID);
+ deleteFile.setSdlFileName(sdlFileName);
+ return deleteFile;
+ }
+
+ public static DeleteInteractionChoiceSet buildDeleteInteractionChoiceSet(
+ Integer interactionChoiceSetID, Integer correlationID) {
+ DeleteInteractionChoiceSet msg = new DeleteInteractionChoiceSet();
+ msg.setInteractionChoiceSetID(interactionChoiceSetID);
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ public static DeleteSubMenu buildDeleteSubMenu(Integer menuID,
+ Integer correlationID) {
+ DeleteSubMenu msg = new DeleteSubMenu();
+ msg.setCorrelationID(correlationID);
+ msg.setMenuID(menuID);
+
+ return msg;
+ }
+
+ public static ListFiles buildListFiles(Integer correlationID) {
+ ListFiles listFiles = new ListFiles();
+ listFiles.setCorrelationID(correlationID);
+ return listFiles;
+ }
+
+ public static PerformInteraction buildPerformInteraction(
+ Vector<TTSChunk> initChunks, String displayText,
+ Vector<Integer> interactionChoiceSetIDList,
+ Vector<TTSChunk> helpChunks, Vector<TTSChunk> timeoutChunks,
+ InteractionMode interactionMode, Integer timeout,
+ Vector<VrHelpItem> vrHelp, Integer correlationID) {
+ PerformInteraction msg = new PerformInteraction();
+ msg.setInitialPrompt(initChunks);
+ msg.setInitialText(displayText);
+ msg.setInteractionChoiceSetIDList(interactionChoiceSetIDList);
+ msg.setInteractionMode(interactionMode);
+ msg.setTimeout(timeout);
+ msg.setHelpPrompt(helpChunks);
+ msg.setTimeoutPrompt(timeoutChunks);
+ msg.setVrHelp(vrHelp);
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ public static PerformInteraction buildPerformInteraction(
+ String initPrompt, String displayText,
+ Vector<Integer> interactionChoiceSetIDList,
+ String helpPrompt, String timeoutPrompt,
+ InteractionMode interactionMode, Integer timeout, Vector<VrHelpItem> vrHelp,
+ Integer correlationID) {
+ Vector<TTSChunk> initChunks = TTSChunkFactory
+ .createSimpleTTSChunks(initPrompt);
+ Vector<TTSChunk> helpChunks = TTSChunkFactory
+ .createSimpleTTSChunks(helpPrompt);
+ Vector<TTSChunk> timeoutChunks = TTSChunkFactory
+ .createSimpleTTSChunks(timeoutPrompt);
+ return buildPerformInteraction(initChunks,
+ displayText, interactionChoiceSetIDList, helpChunks,
+ timeoutChunks, interactionMode, timeout, vrHelp, correlationID);
+ }
+
+ public static PerformInteraction buildPerformInteraction(
+ String initPrompt, String displayText,
+ Integer interactionChoiceSetID,
+ String helpPrompt, String timeoutPrompt,
+ InteractionMode interactionMode, Integer timeout, Vector<VrHelpItem> vrHelp,
+ Integer correlationID) {
+ Vector<Integer> interactionChoiceSetIDs = new Vector<Integer>();
+ interactionChoiceSetIDs.add(interactionChoiceSetID);
+
+ return buildPerformInteraction(
+ initPrompt, displayText, interactionChoiceSetIDs,
+ helpPrompt, timeoutPrompt, interactionMode,
+ timeout, vrHelp, correlationID);
+ }
+
+ public static PerformInteraction buildPerformInteraction(String initPrompt,
+ String displayText, Integer interactionChoiceSetID, Vector<VrHelpItem> vrHelp,
+ Integer correlationID) {
+
+ return buildPerformInteraction(initPrompt, displayText,
+ interactionChoiceSetID, null, null,
+ InteractionMode.BOTH, null, vrHelp, correlationID);
+ }
+
+ public static PerformInteraction buildPerformInteraction(
+ Vector<TTSChunk> initChunks, String displayText,
+ Vector<Integer> interactionChoiceSetIDList,
+ Vector<TTSChunk> helpChunks, Vector<TTSChunk> timeoutChunks,
+ InteractionMode interactionMode, Integer timeout,
+ Integer correlationID) {
+ PerformInteraction msg = new PerformInteraction();
+ msg.setInitialPrompt(initChunks);
+ msg.setInitialText(displayText);
+ msg.setInteractionChoiceSetIDList(interactionChoiceSetIDList);
+ msg.setInteractionMode(interactionMode);
+ msg.setTimeout(timeout);
+ msg.setHelpPrompt(helpChunks);
+ msg.setTimeoutPrompt(timeoutChunks);
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ public static PerformInteraction buildPerformInteraction(
+ String initPrompt, String displayText,
+ Vector<Integer> interactionChoiceSetIDList,
+ String helpPrompt, String timeoutPrompt,
+ InteractionMode interactionMode, Integer timeout,
+ Integer correlationID) {
+ Vector<TTSChunk> initChunks = TTSChunkFactory
+ .createSimpleTTSChunks(initPrompt);
+ Vector<TTSChunk> helpChunks = TTSChunkFactory
+ .createSimpleTTSChunks(helpPrompt);
+ Vector<TTSChunk> timeoutChunks = TTSChunkFactory
+ .createSimpleTTSChunks(timeoutPrompt);
+ return buildPerformInteraction(initChunks,
+ displayText, interactionChoiceSetIDList, helpChunks,
+ timeoutChunks, interactionMode, timeout, correlationID);
+ }
+
+ public static PerformInteraction buildPerformInteraction(
+ String initPrompt, String displayText,
+ Integer interactionChoiceSetID,
+ String helpPrompt, String timeoutPrompt,
+ InteractionMode interactionMode, Integer timeout,
+ Integer correlationID) {
+ Vector<Integer> interactionChoiceSetIDs = new Vector<Integer>();
+ interactionChoiceSetIDs.add(interactionChoiceSetID);
+
+ return buildPerformInteraction(
+ initPrompt, displayText, interactionChoiceSetIDs,
+ helpPrompt, timeoutPrompt, interactionMode,
+ timeout, correlationID);
+ }
+
+ public static PerformInteraction buildPerformInteraction(String initPrompt,
+ String displayText, Integer interactionChoiceSetID,
+ Integer correlationID) {
+
+ return buildPerformInteraction(initPrompt, displayText,
+ interactionChoiceSetID, null, null,
+ InteractionMode.BOTH, null, correlationID);
+ }
+
+ @Deprecated
+ public static PerformInteraction buildPerformInteraction(
+ Vector<TTSChunk> initChunks, String displayText,
+ Vector<Integer> interactionChoiceSetIDList,
+ Vector<TTSChunk> helpChunks, InteractionMode interactionMode,
+ Integer timeout, Integer correlationID) {
+ PerformInteraction msg = new PerformInteraction();
+ msg.setInitialPrompt(initChunks);
+ msg.setInitialText(displayText);
+ msg.setInteractionChoiceSetIDList(interactionChoiceSetIDList);
+ msg.setInteractionMode(interactionMode);
+ msg.setTimeout(timeout);
+ msg.setHelpPrompt(helpChunks);
+ msg.setCorrelationID(correlationID);
+ return msg;
+ }
+
+ @Deprecated
+ public static PerformInteraction buildPerformInteraction(String initPrompt,
+ String displayText, Vector<Integer> interactionChoiceSetIDList,
+ String helpPrompt, InteractionMode interactionMode,
+ Integer timeout, Integer correlationID) {
+ Vector<TTSChunk> initChunks = TTSChunkFactory
+ .createSimpleTTSChunks(initPrompt);
+ Vector<TTSChunk> helpChunks = TTSChunkFactory
+ .createSimpleTTSChunks(helpPrompt);
+ PerformInteraction msg = buildPerformInteraction(initChunks,
+ displayText, interactionChoiceSetIDList, helpChunks,
+ interactionMode, timeout, correlationID);
+ return msg;
+ }
+
+ public static PutFile buildPutFile(String sdlFileName, FileType fileType,
+ Boolean persistentFile, byte[] fileData, Integer correlationID) {
+ PutFile putFile = new PutFile();
+ putFile.setCorrelationID(correlationID);
+ putFile.setSdlFileName(sdlFileName);
+ putFile.setFileType(fileType);
+ putFile.setPersistentFile(persistentFile);
+ putFile.setBulkData(fileData);
+ putFile.setCRC(fileData);
+ return putFile;
+ }
+
+ @Deprecated
+ public static PutFile buildPutFile(String sdlFileName, Integer iOffset, Integer iLength) {
+ PutFile putFile = new PutFile();
+ putFile.setCorrelationID(10000);
+ putFile.setSdlFileName(sdlFileName);
+ putFile.setFileType(FileType.BINARY);
+ putFile.setSystemFile(true);
+ putFile.setOffset(iOffset);
+ putFile.setLength(iLength);
+ return putFile;
+ }
+
+ public static PutFile buildPutFile(String sdlFileName, Long iOffset, Long iLength) {
+ PutFile putFile = new PutFile();
+ putFile.setCorrelationID(10000);
+ putFile.setSdlFileName(sdlFileName);
+ putFile.setFileType(FileType.BINARY);
+ putFile.setSystemFile(true);
+ putFile.setOffset(iOffset);
+ putFile.setLength(iLength);
+ return putFile;
+ }
+
+ @Deprecated
+ public static PutFile buildPutFile(String syncFileName, Integer iOffset, Integer iLength, FileType fileType, Boolean bPersistentFile, Boolean bSystemFile) {
+ PutFile putFile = new PutFile();
+ putFile.setCorrelationID(10000);
+ putFile.setSdlFileName(syncFileName);
+ putFile.setFileType(fileType);
+ putFile.setPersistentFile(bPersistentFile);
+ putFile.setSystemFile(bSystemFile);
+ putFile.setOffset(iOffset);
+ putFile.setLength(iLength);
+ return putFile;
+ }
+
+ public static PutFile buildPutFile(String syncFileName, Long iOffset, Long iLength, FileType fileType, Boolean bPersistentFile, Boolean bSystemFile) {
+ PutFile putFile = new PutFile();
+ putFile.setCorrelationID(10000);
+ putFile.setSdlFileName(syncFileName);
+ putFile.setFileType(fileType);
+ putFile.setPersistentFile(bPersistentFile);
+ putFile.setSystemFile(bSystemFile);
+ putFile.setOffset(iOffset);
+ putFile.setLength(iLength);
+ return putFile;
+ }
+
+ @Deprecated
+ public static PutFile buildPutFile(String sdlFileName, Integer iOffset, Integer iLength, FileType fileType, Boolean bPersistentFile, Boolean bSystemFile, Integer iCorrelationID) {
+ PutFile putFile = new PutFile();
+ putFile.setCorrelationID(iCorrelationID);
+ putFile.setSdlFileName(sdlFileName);
+ putFile.setFileType(fileType);
+ putFile.setPersistentFile(bPersistentFile);
+ putFile.setSystemFile(bSystemFile);
+ putFile.setOffset(iOffset);
+ putFile.setLength(iLength);
+ return putFile;
+ }
+
+ public static PutFile buildPutFile(String sdlFileName, Long iOffset, Long iLength, FileType fileType, Boolean bPersistentFile, Boolean bSystemFile, Boolean isPayloadProtected, Integer iCorrelationID) {
+ PutFile putFile = new PutFile();
+ putFile.setCorrelationID(iCorrelationID);
+ putFile.setSdlFileName(sdlFileName);
+ putFile.setFileType(fileType);
+ putFile.setPersistentFile(bPersistentFile);
+ putFile.setSystemFile(bSystemFile);
+ putFile.setOffset(iOffset);
+ putFile.setLength(iLength);
+ putFile.setPayloadProtected(isPayloadProtected);
+ return putFile;
+ }
+
+ public static RegisterAppInterface buildRegisterAppInterface(String appName, String appID) {
+ return buildRegisterAppInterface(appName, false, appID);
+ }
+
+ public static RegisterAppInterface buildRegisterAppInterface(
+ String appName, Boolean isMediaApp, String appID) {
+
+ return buildRegisterAppInterface(null, appName, null, null, null, isMediaApp,
+ null, null, null, appID, null, null);
+ }
+
+ public static RegisterAppInterface buildRegisterAppInterface(
+ SdlMsgVersion sdlMsgVersion, String appName, Vector<TTSChunk> ttsName,
+ String ngnMediaScreenAppName, Vector<String> vrSynonyms, Boolean isMediaApp,
+ Language languageDesired, Language hmiDisplayLanguageDesired, Vector<AppHMIType> appType,
+ String appID, Integer correlationID, DeviceInfo deviceInfo) {
+ RegisterAppInterface msg = new RegisterAppInterface();
+
+ if (correlationID == null) {
+ correlationID = 1;
+ }
+ msg.setCorrelationID(correlationID);
+
+ if (sdlMsgVersion == null) {
+ sdlMsgVersion = new SdlMsgVersion();
+ sdlMsgVersion.setMajorVersion(Integer.valueOf(SDL_MSG_MAJOR_VERSION));
+ sdlMsgVersion.setMinorVersion(Integer.valueOf(SDL_MSG_MINOR_VERSION));
+ }
+ msg.setSdlMsgVersion(sdlMsgVersion);
+ msg.setDeviceInfo(deviceInfo);
+ msg.setAppName(appName);
+
+ msg.setTtsName(ttsName);
+
+ if (ngnMediaScreenAppName == null) {
+ ngnMediaScreenAppName = appName;
+ }
+
+ msg.setNgnMediaScreenAppName(ngnMediaScreenAppName);
+
+ if (vrSynonyms == null) {
+ vrSynonyms = new Vector<String>();
+ vrSynonyms.add(appName);
+ }
+ msg.setVrSynonyms(vrSynonyms);
+
+ msg.setIsMediaApplication(isMediaApp);
+
+ if (languageDesired == null) {
+ languageDesired = Language.EN_US;
+ }
+ msg.setLanguageDesired(languageDesired);
+
+ if (hmiDisplayLanguageDesired == null) {
+ hmiDisplayLanguageDesired = Language.EN_US;
+ }
+
+ msg.setHmiDisplayLanguageDesired(hmiDisplayLanguageDesired);
+
+ msg.setAppHMIType(appType);
+
+ msg.setAppID(appID);
+
+ return msg;
+ }
+
+ public static SetAppIcon buildSetAppIcon(String sdlFileName,
+ Integer correlationID) {
+ SetAppIcon setAppIcon = new SetAppIcon();
+ setAppIcon.setCorrelationID(correlationID);
+ setAppIcon.setSdlFileName(sdlFileName);
+ return setAppIcon;
+ }
+
+ public static SetGlobalProperties buildSetGlobalProperties(
+ String helpPrompt, String timeoutPrompt, Integer correlationID) {
+ return buildSetGlobalProperties(TTSChunkFactory
+ .createSimpleTTSChunks(helpPrompt), TTSChunkFactory
+ .createSimpleTTSChunks(timeoutPrompt), correlationID);
+ }
+
+ public static SetGlobalProperties buildSetGlobalProperties(
+ Vector<TTSChunk> helpChunks, Vector<TTSChunk> timeoutChunks,
+ Integer correlationID) {
+ SetGlobalProperties req = new SetGlobalProperties();
+ req.setCorrelationID(correlationID);
+
+ req.setHelpPrompt(helpChunks);
+ req.setTimeoutPrompt(timeoutChunks);
+
+ return req;
+ }
+
+ public static SetGlobalProperties buildSetGlobalProperties(
+ String helpPrompt, String timeoutPrompt, String vrHelpTitle,
+ Vector<VrHelpItem> vrHelp, Integer correlationID) {
+ return buildSetGlobalProperties(TTSChunkFactory
+ .createSimpleTTSChunks(helpPrompt), TTSChunkFactory
+ .createSimpleTTSChunks(timeoutPrompt), vrHelpTitle, vrHelp, correlationID);
+ }
+
+ public static SetGlobalProperties buildSetGlobalProperties(
+ Vector<TTSChunk> helpChunks, Vector<TTSChunk> timeoutChunks,
+ String vrHelpTitle, Vector<VrHelpItem> vrHelp,
+ Integer correlationID) {
+ SetGlobalProperties req = new SetGlobalProperties();
+ req.setCorrelationID(correlationID);
+
+ req.setHelpPrompt(helpChunks);
+ req.setTimeoutPrompt(timeoutChunks);
+
+ req.setVrHelpTitle(vrHelpTitle);
+ req.setVrHelp(vrHelp);
+
+ return req;
+ }
+
+ public static SetMediaClockTimer buildSetMediaClockTimer(Integer hours,
+ Integer minutes, Integer seconds, UpdateMode updateMode,
+ Integer correlationID) {
+
+ SetMediaClockTimer msg = new SetMediaClockTimer();
+ if (hours != null || minutes != null || seconds != null) {
+ StartTime startTime = new StartTime();
+ msg.setStartTime(startTime);
+ startTime.setHours(hours);
+ startTime.setMinutes(minutes);
+ startTime.setSeconds(seconds);
+ }
+
+ msg.setUpdateMode(updateMode);
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ @Deprecated
+ public static SetMediaClockTimer buildSetMediaClockTimer(
+ UpdateMode updateMode, Integer correlationID) {
+ Integer hours = null;
+ Integer minutes = null;
+ Integer seconds = null;
+
+ SetMediaClockTimer msg = buildSetMediaClockTimer(hours, minutes,
+ seconds, updateMode, correlationID);
+ return msg;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Show buildShow(String mainText1, String mainText2,
+ String mainText3, String mainText4,
+ String statusBar, String mediaClock, String mediaTrack,
+ Image graphic, Vector<SoftButton> softButtons, Vector <String> customPresets,
+ TextAlignment alignment, Integer correlationID) {
+ Show msg = new Show();
+ msg.setCorrelationID(correlationID);
+ msg.setMainField1(mainText1);
+ msg.setMainField2(mainText2);
+ msg.setStatusBar(statusBar);
+ msg.setMediaClock(mediaClock);
+ msg.setMediaTrack(mediaTrack);
+ msg.setAlignment(alignment);
+ msg.setMainField3(mainText3);
+ msg.setMainField4(mainText4);
+ msg.setGraphic(graphic);
+ msg.setSoftButtons(softButtons);
+ msg.setCustomPresets(customPresets);
+
+ return msg;
+ }
+
+ public static Show buildShow(String mainText1, String mainText2, String mainText3, String mainText4,
+ TextAlignment alignment, Integer correlationID) {
+ Show msg = buildShow(mainText1, mainText2, mainText3, mainText4, null, null, null, null, null, null, alignment,
+ correlationID);
+ return msg;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static Show buildShow(String mainText1, String mainText2,
+ String statusBar, String mediaClock, String mediaTrack,
+ TextAlignment alignment, Integer correlationID) {
+ Show msg = new Show();
+ msg.setCorrelationID(correlationID);
+ msg.setMainField1(mainText1);
+ msg.setMainField2(mainText2);
+ msg.setStatusBar(statusBar);
+ msg.setMediaClock(mediaClock);
+ msg.setMediaTrack(mediaTrack);
+ msg.setAlignment(alignment);
+
+ return msg;
+ }
+
+ public static Show buildShow(String mainText1, String mainText2,
+ TextAlignment alignment, Integer correlationID) {
+ Show msg = buildShow(mainText1, mainText2, null, null, null, alignment,
+ correlationID);
+ return msg;
+ }
+
+ public static Speak buildSpeak(String ttsText, Integer correlationID) {
+ Speak msg = buildSpeak(TTSChunkFactory.createSimpleTTSChunks(ttsText),
+ correlationID);
+ return msg;
+ }
+
+ public static Speak buildSpeak(Vector<TTSChunk> ttsChunks,
+ Integer correlationID) {
+
+ Speak msg = new Speak();
+ msg.setCorrelationID(correlationID);
+
+ msg.setTtsChunks(ttsChunks);
+
+ return msg;
+ }
+
+ public static SubscribeButton buildSubscribeButton(ButtonName buttonName,
+ Integer correlationID) {
+
+ SubscribeButton msg = new SubscribeButton();
+ msg.setCorrelationID(correlationID);
+ msg.setButtonName(buttonName);
+
+ return msg;
+ }
+
+ public static UnregisterAppInterface buildUnregisterAppInterface(
+ Integer correlationID) {
+ UnregisterAppInterface msg = new UnregisterAppInterface();
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ public static UnsubscribeButton buildUnsubscribeButton(
+ ButtonName buttonName, Integer correlationID) {
+
+ UnsubscribeButton msg = new UnsubscribeButton();
+ msg.setCorrelationID(correlationID);
+ msg.setButtonName(buttonName);
+
+ return msg;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static SubscribeVehicleData BuildSubscribeVehicleData(boolean gps, boolean speed, boolean rpm, boolean fuelLevel, boolean fuelLevel_State,
+ boolean instantFuelConsumption, boolean externalTemperature, boolean prndl, boolean tirePressure,
+ boolean odometer, boolean beltStatus, boolean bodyInformation, boolean deviceStatus,
+ boolean driverBraking, Integer correlationID)
+ {
+ SubscribeVehicleData msg = new SubscribeVehicleData();
+ msg.setGps(gps);
+ msg.setSpeed(speed);
+ msg.setRpm(rpm);
+ msg.setFuelLevel(fuelLevel);
+ msg.setFuelLevel_State(fuelLevel_State);
+ msg.setInstantFuelConsumption(instantFuelConsumption);
+ msg.setExternalTemperature(externalTemperature);
+ msg.setPrndl(prndl);
+ msg.setTirePressure(tirePressure);
+ msg.setOdometer(odometer);
+ msg.setBeltStatus(beltStatus);
+ msg.setBodyInformation(bodyInformation);
+ msg.setDeviceStatus(deviceStatus);
+ msg.setDriverBraking(driverBraking);
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static UnsubscribeVehicleData BuildUnsubscribeVehicleData(boolean gps, boolean speed, boolean rpm, boolean fuelLevel, boolean fuelLevel_State,
+ boolean instantFuelConsumption, boolean externalTemperature, boolean prndl, boolean tirePressure,
+ boolean odometer, boolean beltStatus, boolean bodyInformation, boolean deviceStatus,
+ boolean driverBraking, Integer correlationID)
+ {
+ UnsubscribeVehicleData msg = new UnsubscribeVehicleData();
+ msg.setGps(gps);
+ msg.setSpeed(speed);
+ msg.setRpm(rpm);
+ msg.setFuelLevel(fuelLevel);
+ msg.setFuelLevel_State(fuelLevel_State);
+ msg.setInstantFuelConsumption(instantFuelConsumption);
+ msg.setExternalTemperature(externalTemperature);
+ msg.setPrndl(prndl);
+ msg.setTirePressure(tirePressure);
+ msg.setOdometer(odometer);
+ msg.setBeltStatus(beltStatus);
+ msg.setBodyInformation(bodyInformation);
+ msg.setDeviceStatus(deviceStatus);
+ msg.setDriverBraking(driverBraking);
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ @SuppressWarnings("deprecation")
+ public static GetVehicleData BuildGetVehicleData(boolean gps, boolean speed, boolean rpm, boolean fuelLevel, boolean fuelLevel_State,
+ boolean instantFuelConsumption, boolean externalTemperature, boolean vin, boolean prndl, boolean tirePressure,
+ boolean odometer, boolean beltStatus, boolean bodyInformation, boolean deviceStatus,
+ boolean driverBraking, Integer correlationID)
+ {
+ GetVehicleData msg = new GetVehicleData();
+ msg.setGps(gps);
+ msg.setSpeed(speed);
+ msg.setRpm(rpm);
+ msg.setFuelLevel(fuelLevel);
+ msg.setFuelLevel_State(fuelLevel_State);
+ msg.setInstantFuelConsumption(instantFuelConsumption);
+ msg.setExternalTemperature(externalTemperature);
+ msg.setVin(vin);
+ msg.setPrndl(prndl);
+ msg.setTirePressure(tirePressure);
+ msg.setOdometer(odometer);
+ msg.setBeltStatus(beltStatus);
+ msg.setBodyInformation(bodyInformation);
+ msg.setDeviceStatus(deviceStatus);
+ msg.setDriverBraking(driverBraking);
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ public static ScrollableMessage BuildScrollableMessage(String scrollableMessageBody, Integer timeout, Vector<SoftButton> softButtons, Integer correlationID)
+ {
+ ScrollableMessage msg = new ScrollableMessage();
+ msg.setCorrelationID(correlationID);
+ msg.setScrollableMessageBody(scrollableMessageBody);
+ msg.setTimeout(timeout);
+ msg.setSoftButtons(softButtons);
+
+ return msg;
+ }
+
+ public static Slider BuildSlider(Integer numTicks, Integer position, String sliderHeader, Vector<String> sliderFooter, Integer timeout, Integer correlationID)
+ {
+ Slider msg = new Slider();
+ msg.setCorrelationID(correlationID);
+ msg.setNumTicks(numTicks);
+ msg.setPosition(position);
+ msg.setSliderHeader(sliderHeader);
+ msg.setSliderFooter(sliderFooter);
+ msg.setTimeout(timeout);
+
+ return msg;
+ }
+
+ public static ChangeRegistration BuildChangeRegistration(Language language, Language hmiDisplayLanguage, Integer correlationID)
+ {
+ ChangeRegistration msg = new ChangeRegistration();
+ msg.setCorrelationID(correlationID);
+ msg.setLanguage(language);
+ msg.setHmiDisplayLanguage(hmiDisplayLanguage);
+
+ return msg;
+ }
+
+ public static SetDisplayLayout BuildSetDisplayLayout(String displayLayout, Integer correlationID)
+ {
+ SetDisplayLayout msg = new SetDisplayLayout();
+ msg.setCorrelationID(correlationID);
+ msg.setDisplayLayout(displayLayout);
+
+ return msg;
+ }
+
+ public static PerformAudioPassThru BuildPerformAudioPassThru(String ttsText, String audioPassThruDisplayText1, String audioPassThruDisplayText2,
+ SamplingRate samplingRate, Integer maxDuration, BitsPerSample bitsPerSample,
+ AudioType audioType, Boolean muteAudio, Integer correlationID)
+ {
+ Vector<TTSChunk> chunks = TTSChunkFactory
+ .createSimpleTTSChunks(ttsText);
+
+ PerformAudioPassThru msg = BuildPerformAudioPassThru(chunks, audioPassThruDisplayText1, audioPassThruDisplayText2,
+ samplingRate, maxDuration, bitsPerSample,audioType, muteAudio, correlationID);
+
+ return msg;
+ }
+
+ public static PerformAudioPassThru BuildPerformAudioPassThru(Vector<TTSChunk> initialPrompt, String audioPassThruDisplayText1, String audioPassThruDisplayText2,
+ SamplingRate samplingRate, Integer maxDuration, BitsPerSample bitsPerSample,
+ AudioType audioType, Boolean muteAudio, Integer correlationID)
+ {
+ PerformAudioPassThru msg = new PerformAudioPassThru();
+ msg.setCorrelationID(correlationID);
+ msg.setInitialPrompt(initialPrompt);
+ msg.setAudioPassThruDisplayText1(audioPassThruDisplayText1);
+ msg.setAudioPassThruDisplayText2(audioPassThruDisplayText2);
+ msg.setSamplingRate(samplingRate);
+ msg.setMaxDuration(maxDuration);
+ msg.setBitsPerSample(bitsPerSample);
+ msg.setAudioType(audioType);
+ msg.setMuteAudio(muteAudio);
+
+ return msg;
+ }
+
+ public static EndAudioPassThru BuildEndAudioPassThru(Integer correlationID)
+ {
+ EndAudioPassThru msg = new EndAudioPassThru();
+ msg.setCorrelationID(correlationID);
+
+ return msg;
+ }
+
+ public static DeviceInfo BuildDeviceInfo(String carrierName)
+ {
+ DeviceInfo msg = new DeviceInfo();
+ //FIXME msg.setHardware(android.os.Build.MODEL);
+ msg.setOs(DeviceInfo.DEVICE_OS);
+ //msg.setOsVersion(Build.VERSION.RELEASE);
+ msg.setCarrier(carrierName);
+ return msg;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCResponse.java b/base/src/main/java/com/smartdevicelink/proxy/RPCResponse.java
new file mode 100644
index 000000000..0e6388570
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCResponse.java
@@ -0,0 +1,183 @@
+/**
+ *
+ */
+package com.smartdevicelink.proxy;
+
+import android.support.annotation.NonNull;
+
+import java.util.Hashtable;
+
+import com.smartdevicelink.proxy.rpc.enums.Result;
+
+/**
+ * Result sent by SDL after an RPC is processed, consists of four parts:
+ * <ul>
+ * <li>
+ * CorrelationID:
+ * <ul>
+ * An integer value correlating the response to the corresponding request.
+ * </ul>
+ * </li> <li>Success:
+ * <ul>
+ * A Boolean indicating whether the original request was successfully processed.
+ * </ul>
+ * </li> <li>ResultCode:
+ * <ul>
+ *
+ * <p>The result code provides additional information about a response returning a
+ * failed outcome.</p>
+ *
+ *
+ * <p>Any response can have at least one, or possibly more, of the following result
+ * code values: SUCCESS, INVALID_DATA, OUT_OF_MEMORY, TOO_MANY_PENDING_REQUESTS,
+ * APPLICATION_NOT_REGISTERED, GENERIC_ERROR,REJECTED.</p>
+ *
+ *
+ * <p>Any additional result codes for a given operation can be found in related
+ * RPCs</p>
+ *
+ * </ul>
+ * </li> <li>Info:
+ * <ul>
+ * A string of text representing additional information returned from SDL. This
+ * could be useful in debugging.
+ * </ul>
+ * </li>
+ * </ul>
+ */
+public class RPCResponse extends RPCMessage {
+ public static final String KEY_SUCCESS = "success";
+ public static final String KEY_INFO = "info";
+ public static final String KEY_RESULT_CODE = "resultCode";
+ /**
+ *<p>Constructs a newly allocated RPCResponse object using function name</p>
+ *@param functionName a string that indicates the function's name
+ */
+ public RPCResponse(String functionName) {
+ super(functionName, RPCMessage.KEY_RESPONSE);
+ }
+ /**
+ *<p>Constructs a newly allocated RPCResponse object indicated by the Hashtable parameter</p>
+ *@param hash The Hashtable to use
+ */
+ public RPCResponse(Hashtable<String, Object> hash) {
+ super(hash);
+ }
+ /**
+ *<p>Constructs a newly allocated RPCResponse object using a RPCMessage object</p>
+ *@param rpcMsg The {@linkplain RPCMessage} to use
+ */
+ public RPCResponse(RPCMessage rpcMsg) {
+ super(preprocessMsg(rpcMsg));
+ }
+
+ static RPCMessage preprocessMsg (RPCMessage rpcMsg) {
+ if (rpcMsg.getMessageType() != RPCMessage.KEY_RESPONSE) {
+ rpcMsg.messageType = RPCMessage.KEY_RESPONSE;
+ }
+
+ return rpcMsg;
+ }
+
+ /**
+ * <p>
+ * Returns correlationID the ID of the request
+ * </p>
+ *
+ * @return int the ID of the request
+ */
+ public Integer getCorrelationID() {
+ return (Integer)function.get(RPCMessage.KEY_CORRELATION_ID);
+ }
+
+ /**
+ * <p>
+ * Set the correlationID
+ * </p>
+ *
+ * @param correlationID
+ * the ID of the response
+ */
+ public void setCorrelationID(Integer correlationID) {
+ if (correlationID != null) {
+ function.put(RPCMessage.KEY_CORRELATION_ID, correlationID );
+ } else {
+ function.remove(RPCMessage.KEY_CORRELATION_ID);
+ }
+ }
+ /**
+ * <p>
+ * Returns Success whether the request is successfully processed
+ * </p>
+ *
+ * @return Boolean the status of whether the request is successfully done
+ */
+ public Boolean getSuccess() {
+ return (Boolean) parameters.get( RPCResponse.KEY_SUCCESS );
+ }
+ /**
+ * <p>
+ * Set the Success status
+ * </p>
+ *
+ * @param success
+ * whether the request is successfully processed
+ */
+ public void setSuccess( @NonNull Boolean success ) {
+ if (success != null) {
+ parameters.put(RPCResponse.KEY_SUCCESS, success );
+ }
+ }
+ /**
+ * <p>
+ * Returns ResultCode additional information about a response returning a failed outcome
+ * </p>
+ *
+ * @return {@linkplain Result} the status of whether the request is successfully done
+ */
+ public Result getResultCode() {
+ Object obj = parameters.get(RPCResponse.KEY_RESULT_CODE);
+ if (obj instanceof Result) {
+ return (Result) obj;
+ } else if (obj instanceof String) {
+ return Result.valueForString((String) obj);
+ }
+ return null;
+ }
+ /**
+ * <p>
+ * Set the additional information about a response returning a failed outcome
+ * </p>
+ *
+ * @param resultCode
+ * whether the request is successfully processed
+ */
+ public void setResultCode( @NonNull Result resultCode ) {
+ if (resultCode != null) {
+ parameters.put(RPCResponse.KEY_RESULT_CODE, resultCode );
+ }
+ }
+ /**
+ * <p>
+ * Returns a string of text representing additional information returned from SDL
+ * </p>
+ *
+ * @return String A string of text representing additional information returned from SDL
+ */
+ public String getInfo() {
+ return (String) parameters.get( RPCResponse.KEY_INFO );
+ }
+ /**
+ * <p>
+ * Set a string of text representing additional information returned from SDL
+ * </p>
+ *
+ * @param info
+ * a string of text representing additional information returned from SDL
+ */
+ public void setInfo( String info ) {
+ if (info != null) {
+ parameters.put(RPCResponse.KEY_INFO, info );
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCStreamController.java b/base/src/main/java/com/smartdevicelink/proxy/RPCStreamController.java
new file mode 100644
index 000000000..0e2af0bc6
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCStreamController.java
@@ -0,0 +1,32 @@
+package com.smartdevicelink.proxy;
+
+import com.smartdevicelink.streaming.StreamRPCPacketizer;
+
+public class RPCStreamController {
+ private StreamRPCPacketizer rpcPacketizer;
+ private Integer iCorrelationID;
+
+ public RPCStreamController(StreamRPCPacketizer rpcPacketizer, Integer iCorrelationID)
+ {
+ this.rpcPacketizer = rpcPacketizer;
+ this.iCorrelationID = iCorrelationID;
+ }
+
+ public Integer getCorrelationID()
+ {
+ return iCorrelationID;
+ }
+
+ public void pause()
+ {
+ rpcPacketizer.pause();
+ }
+ public void resume()
+ {
+ rpcPacketizer.resume();
+ }
+ public void stop()
+ {
+ rpcPacketizer.onPutFileStreamError(null, "Stop Putfile Stream Requested");
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java b/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java
new file mode 100644
index 000000000..2d8c8ce3b
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/RPCStruct.java
@@ -0,0 +1,322 @@
+package com.smartdevicelink.proxy;
+
+import android.util.Log;
+
+import com.smartdevicelink.marshal.JsonRPCMarshaller;
+import com.smartdevicelink.util.Version;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Set;
+
+public class RPCStruct {
+ public static final String KEY_BULK_DATA = "bulkData";
+ public static final String KEY_PROTECTED = "protected";
+
+ private byte[] _bulkData = null;
+ private Boolean protectedPayload = false;
+
+ private boolean formatRequested = false;
+ private Version rpcSpecVersion = null;
+
+
+ protected Hashtable<String, Object> store = null;
+
+ public boolean getStoreValue(String key) { // for unit testing
+ return store.contains(key);
+ }
+
+ public Hashtable<String,Object> getStore () { // for unit testing
+ return store;
+ }
+
+ public RPCStruct() {
+ store = new Hashtable<String, Object>();
+ }
+
+ protected RPCStruct(RPCStruct rpcs) {
+ this.store = rpcs.store;
+ }
+
+ public RPCStruct(Hashtable<String, Object> hashtable) {
+ store = hashtable;
+ //store = (Hashtable<String, Object>) ObjectCopier.copy(hashtable);
+ }
+
+ public void deserializeJSON(JSONObject jsonObject) throws JSONException {
+ store = JsonRPCMarshaller.deserializeJSONObject(jsonObject);
+
+ }
+
+ // deserializeJSONObject method moved to JsonRPCMarshaller for consistency
+ // Keep reference here for backwards compatibility
+ @Deprecated
+ public static Hashtable<String, Object> deserializeJSONObject(JSONObject jsonObject)
+ throws JSONException {
+ return JsonRPCMarshaller.deserializeJSONObject(jsonObject);
+ }
+
+ public JSONObject serializeJSON() throws JSONException {
+ return JsonRPCMarshaller.serializeHashtable(store);
+ }
+
+ @SuppressWarnings("unchecked")
+ public JSONObject serializeJSON(byte protocolVersion) throws JSONException {
+ if (protocolVersion > 1) {
+ String messageType = getMessageTypeName(store.keySet());
+ Hashtable<String, Object> function = (Hashtable<String, Object>) store.get(messageType);
+ Hashtable<String, Object> parameters = (Hashtable<String, Object>) function.get(RPCMessage.KEY_PARAMETERS);
+ return JsonRPCMarshaller.serializeHashtable(parameters);
+ } else return JsonRPCMarshaller.serializeHashtable(store);
+ }
+
+ /**
+ * This method should clean the the RPC to make sure it is compliant with the spec.
+ * <br><br><b> NOTE:</b> Super needs to be called at the END of the method
+ *
+ * @param rpcVersion the rpc spec version that has been negotiated. If value is null the
+ * the max value of RPC spec version this library supports should be used.
+ * @param formatParams if true, the format method will be called on subsequent params
+ */
+ public void format(Version rpcVersion, boolean formatParams){
+ formatRequested = true;
+ rpcSpecVersion = rpcVersion;
+ //Should override this method when breaking changes are made to the RPC spec
+ if(formatParams && store != null){
+ Hashtable<String, Object> parameters;
+
+ if(this instanceof RPCMessage) {
+ //If this is a message (request, response, notification) the parameters have to be
+ //retrieved from the store object.
+ String messageType = getMessageTypeName(store.keySet());
+ Hashtable<String, Object> function = (Hashtable<String, Object>) store.get(messageType);
+ parameters = (Hashtable<String, Object>) function.get(RPCMessage.KEY_PARAMETERS);
+ } else {
+ //If this is just an RPC struct the store itself should be used
+ parameters = store;
+ }
+
+ if (parameters != null) {
+ for(Object value:parameters.values()){
+ internalFormat(rpcVersion, value);
+ }
+ }
+ }
+ }
+
+ /**
+ * Cycles through parameters in this RPC to ensure they all get formated
+ * @param rpcVersion version of the rpc spec that should be used to format this rpc
+ * @param value the object to investigate if it needs to be formated
+ */
+ private void internalFormat(Version rpcVersion, Object value) {
+ if(value instanceof RPCStruct) {
+ ((RPCStruct)value).format(rpcVersion,true);
+ } else if(value instanceof List<?>) {
+ List<?> list = (List<?>)value;
+ if(list != null && list.size() > 0) {
+ for(Object listItem: list){
+ internalFormat(rpcVersion, listItem);
+ }
+ }
+ }
+ }
+
+
+ public byte[] getBulkData() {
+ return this._bulkData;
+ }
+
+ public void setBulkData(byte[] bulkData) {
+ if (bulkData != null) {
+ this._bulkData = new byte[bulkData.length];
+ System.arraycopy(bulkData, 0, _bulkData, 0, bulkData.length);
+ }
+ else{
+ this._bulkData = null;
+ }
+ }
+
+ public void setPayloadProtected(Boolean bVal) {
+ protectedPayload = bVal;
+ }
+
+ public Boolean isPayloadProtected() {
+ return protectedPayload;
+ }
+
+ protected String getMessageTypeName(Set<String> keys) {
+ for (String key : keys) {
+ if (key == null) {
+ continue;
+ }
+ if (key.equals(RPCMessage.KEY_REQUEST) || key.equals(RPCMessage.KEY_RESPONSE) ||
+ key.equals(RPCMessage.KEY_NOTIFICATION)) {
+ return key;
+ }
+ }
+ return null;
+ }
+
+ protected boolean hasKey(Set<String> keys, String keyName) {
+ for (String key : keys) {
+ if (key == null) {
+ continue;
+ }
+ if (key.equals(keyName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // Generalized Getters and Setters
+
+ public void setValue(String key, Object value){
+ if (value != null) {
+ store.put(key, value);
+ } else {
+ store.remove(key);
+ }
+ }
+
+ public Object getValue(String key) {
+ return store.get(key);
+ }
+
+ public Object getObject(Class tClass, String key) {
+ Object obj = store.get(key);
+ return formatObject(tClass, obj);
+ }
+
+ // Helper methods
+
+ /**
+ * @param tClass a Class to cast Objects to
+ * @param obj Object returned from a stored hashtable
+ * @return A null object if obj is null or if none of the following is true:
+ * a) obj is an instance of tClass
+ * b) obj is an instance of String and it tClass has a valid `valueForString` method
+ * c) obj is an instance of a Hashtable
+ * d) obj is an instance of a List
+ */
+ protected Object formatObject(Class tClass, Object obj){
+ if(obj == null){
+ return null;
+ } else if (tClass.isInstance(obj)) {
+ return obj;
+ } else if (obj instanceof String) {
+ return getValueForString(tClass, (String) obj);
+ } else if (obj instanceof Hashtable) {
+ try {
+ Constructor constructor = tClass.getConstructor(Hashtable.class);
+ Object customObject = constructor.newInstance((Hashtable<String, Object>) obj);
+ if(formatRequested && customObject instanceof RPCStruct){
+ ((RPCStruct)customObject).format(rpcSpecVersion,true);
+ }
+
+ return customObject;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else if (obj instanceof List<?>) {
+ List<?> list = (List<?>) obj;
+ if (list != null && list.size() > 0) {
+ Object item = list.get(0);
+ if (tClass.isInstance(item)) {
+ return list;
+ } else if (item instanceof Hashtable) {
+ List<Object> newList = new ArrayList<Object>();
+ Object customObject;
+ for (Object hashObj : list) {
+ try {
+ Constructor constructor = tClass.getConstructor(Hashtable.class);
+ customObject = constructor.newInstance((Hashtable<String, Object>) hashObj);
+ if(formatRequested
+ && customObject != null
+ && customObject instanceof RPCStruct){
+ ((RPCStruct)customObject).format(rpcSpecVersion,true);
+ }
+ newList.add(customObject);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+ return newList;
+ } else if (item instanceof String){
+ List<Object> newList = new ArrayList<Object>();
+ for (Object hashObj : list) {
+ Object toAdd = getValueForString(tClass, (String) hashObj);
+ if (toAdd != null) {
+ newList.add(toAdd);
+ }
+ }
+ return newList;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * @param tClass - a Class with a `valueForString(String s)` method that returns an Object for a given String
+ * @param s - a String to be converted to an Object using a `valueForString(String s)` method
+ * @return An Object converted using a `valueForString(String s)` method in the Class passed in, or a null object if such method does not exist
+ */
+ protected Object getValueForString(Class tClass, String s){
+ Method valueForString = null;
+ try {
+ valueForString = tClass.getDeclaredMethod("valueForString", String.class);
+ } catch (NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ if(valueForString != null){
+ try {
+ Object value = valueForString.invoke(null, (String) s);
+ return value;
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ } catch (InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ }
+ return null;
+ }
+
+ // Common Object Getters
+ public String getString(String key) {
+ return (String) store.get(key);
+ }
+
+ public Integer getInteger(String key) {
+ return (Integer) store.get(key);
+ }
+
+ public Double getDouble(String key) {
+ return (Double) store.get(key);
+ }
+
+ public Float getFloat(String key) {
+ return (Float) store.get(key);
+ }
+
+ public Boolean getBoolean(String key) { return (Boolean) store.get(key); }
+
+ public Long getLong(String key){
+ Object result = store.get(key);
+ if (result instanceof Integer) {
+ return ((Integer) result).longValue();
+ }else if(result instanceof Long){
+ return (Long) result;
+ }
+ return null;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/SdlProxyConfigurationResources.java b/base/src/main/java/com/smartdevicelink/proxy/SdlProxyConfigurationResources.java
new file mode 100644
index 000000000..ac7e0bf93
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/SdlProxyConfigurationResources.java
@@ -0,0 +1,34 @@
+package com.smartdevicelink.proxy;
+
+//import android.telephony.TelephonyManager;
+
+public class SdlProxyConfigurationResources {
+ /*private String _sdlConfigurationFilePath;
+ private TelephonyManager _telephonyManager;
+
+ public SdlProxyConfigurationResources() {
+ this(null, null);
+ }
+
+ public SdlProxyConfigurationResources(String sdlConfigurationFilePath,
+ TelephonyManager telephonyManager) {
+ _sdlConfigurationFilePath = sdlConfigurationFilePath;
+ _telephonyManager = telephonyManager;
+ }
+
+ public void setSdlConfigurationFilePath(String sdlConfigurationFilePath) {
+ _sdlConfigurationFilePath = sdlConfigurationFilePath;
+ }
+
+ public String getSdlConfigurationFilePath() {
+ return _sdlConfigurationFilePath;
+ }
+
+ public void setTelephonyManager(TelephonyManager telephonyManager) {
+ _telephonyManager = telephonyManager;
+ }
+
+ public TelephonyManager getTelephonyManager() {
+ return _telephonyManager;
+ } */
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/SystemCapabilityManager.java b/base/src/main/java/com/smartdevicelink/proxy/SystemCapabilityManager.java
new file mode 100644
index 000000000..6cf2d496c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/SystemCapabilityManager.java
@@ -0,0 +1,250 @@
+package com.smartdevicelink.proxy;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener;
+import com.smartdevicelink.proxy.rpc.*;
+import com.smartdevicelink.proxy.rpc.enums.Result;
+import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
+import com.smartdevicelink.util.CorrelationIdGenerator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class SystemCapabilityManager {
+ private final HashMap<SystemCapabilityType, Object> cachedSystemCapabilities;
+ private final HashMap<SystemCapabilityType, CopyOnWriteArrayList<OnSystemCapabilityListener>> onSystemCapabilityListeners;
+ private final Object LISTENER_LOCK;
+ private final ISdl callback;
+ private OnRPCListener rpcListener;
+
+ public SystemCapabilityManager(ISdl callback){
+ this.callback = callback;
+ this.LISTENER_LOCK = new Object();
+ this.onSystemCapabilityListeners = new HashMap<>();
+ this.cachedSystemCapabilities = new HashMap<>();
+
+ setupRpcListeners();
+ }
+
+ public void parseRAIResponse(RegisterAppInterfaceResponse response){
+ if(response!=null && response.getSuccess()) {
+ setCapability(SystemCapabilityType.HMI, response.getHmiCapabilities());
+ setCapability(SystemCapabilityType.DISPLAY, response.getDisplayCapabilities());
+ setCapability(SystemCapabilityType.AUDIO_PASSTHROUGH, response.getAudioPassThruCapabilities());
+ setCapability(SystemCapabilityType.PCM_STREAMING, response.getPcmStreamingCapabilities());
+ setCapability(SystemCapabilityType.BUTTON, response.getButtonCapabilities());
+ setCapability(SystemCapabilityType.HMI_ZONE, response.getHmiZoneCapabilities());
+ setCapability(SystemCapabilityType.PRESET_BANK, response.getPresetBankCapabilities());
+ setCapability(SystemCapabilityType.SOFTBUTTON, response.getSoftButtonCapabilities());
+ setCapability(SystemCapabilityType.SPEECH, response.getSpeechCapabilities());
+ setCapability(SystemCapabilityType.VOICE_RECOGNITION, response.getVrCapabilities());
+ }
+ }
+
+ private void setupRpcListeners(){
+ rpcListener = new OnRPCListener() {
+ @Override
+ public void onReceived(RPCMessage message) {
+ if (message != null && RPCMessage.KEY_RESPONSE.equals(message.getMessageType())) {
+ switch (message.getFunctionID()) {
+ case SET_DISPLAY_LAYOUT:
+ SetDisplayLayoutResponse response = (SetDisplayLayoutResponse)message;
+ setCapability(SystemCapabilityType.DISPLAY, response.getDisplayCapabilities());
+ setCapability(SystemCapabilityType.BUTTON, response.getButtonCapabilities());
+ setCapability(SystemCapabilityType.PRESET_BANK, response.getPresetBankCapabilities());
+ setCapability(SystemCapabilityType.SOFTBUTTON, response.getSoftButtonCapabilities());
+ break;
+ }
+ }
+ }
+ };
+
+ if(callback != null){
+ callback.addOnRPCListener(FunctionID.SET_DISPLAY_LAYOUT, rpcListener);
+ }
+ }
+
+ /**
+ * Sets a capability in the cached map. This should only be done when an RPC is received and contains updates to the capability
+ * that is being cached in the SystemCapabilityManager.
+ * @param systemCapabilityType
+ * @param capability
+ */
+ public synchronized void setCapability(SystemCapabilityType systemCapabilityType, Object capability){
+ cachedSystemCapabilities.put(systemCapabilityType, capability);
+ notifyListeners(systemCapabilityType, capability);
+ }
+
+ /**
+ * Notify listners in the list about the new retrieved capability
+ * @param systemCapabilityType
+ * @param capability
+ */
+ private void notifyListeners(SystemCapabilityType systemCapabilityType, Object capability) {
+ synchronized(LISTENER_LOCK){
+ CopyOnWriteArrayList<OnSystemCapabilityListener> listeners = onSystemCapabilityListeners.get(systemCapabilityType);
+ if(listeners != null && listeners.size() > 0) {
+ for (OnSystemCapabilityListener listener : listeners) {
+ listener.onCapabilityRetrieved(capability);
+ }
+ }
+ }
+ }
+
+ /**
+ * Ability to see if the connected module supports the given capability. Useful to check before
+ * attempting to query for capabilities that require asynchronous calls to initialize.
+ * @param type the SystemCapabilityType that is to be checked
+ * @return if that capability is supported with the current, connected module
+ */
+ public boolean isCapabilitySupported(SystemCapabilityType type){
+ if(cachedSystemCapabilities.get(type) != null){
+ //The capability exists in the map and is not null
+ return true;
+ }else if(cachedSystemCapabilities.containsKey(SystemCapabilityType.HMI)){
+ HMICapabilities hmiCapabilities = ((HMICapabilities)cachedSystemCapabilities.get(SystemCapabilityType.HMI));
+ switch (type) {
+ case NAVIGATION:
+ return hmiCapabilities.isNavigationAvailable();
+ case PHONE_CALL:
+ return hmiCapabilities.isPhoneCallAvailable();
+ case VIDEO_STREAMING:
+ return hmiCapabilities.isVideoStreamingAvailable();
+ case REMOTE_CONTROL:
+ return hmiCapabilities.isRemoteControlAvailable();
+ default:
+ return false;
+ }
+ }else{
+ return false;
+ }
+ }
+ /**
+ * @param systemCapabilityType Type of capability desired
+ * @param scListener callback to execute upon retrieving capability
+ */
+ public void getCapability(final SystemCapabilityType systemCapabilityType, final OnSystemCapabilityListener scListener){
+ Object capability = cachedSystemCapabilities.get(systemCapabilityType);
+ if(capability != null){
+ scListener.onCapabilityRetrieved(capability);
+ return;
+ }else if(scListener == null){
+ return;
+ }
+
+ retrieveCapability(systemCapabilityType, scListener);
+ }
+
+ /**
+ * @param systemCapabilityType Type of capability desired
+ * @return Desired capability if it is cached in the manager, otherwise returns a null object
+ * and works in the background to retrieve the capability for the next call
+ */
+ public Object getCapability(final SystemCapabilityType systemCapabilityType){
+ Object capability = cachedSystemCapabilities.get(systemCapabilityType);
+ if(capability != null){
+ return capability;
+ }
+
+ retrieveCapability(systemCapabilityType, null);
+ return null;
+ }
+
+ /**
+ * Add a listener to be called whenever a new capability is retrieved
+ * @param systemCapabilityType Type of capability desired
+ * @param listener callback to execute upon retrieving capability
+ */
+ public void addOnSystemCapabilityListener(final SystemCapabilityType systemCapabilityType, final OnSystemCapabilityListener listener){
+ getCapability(systemCapabilityType, listener);
+ synchronized(LISTENER_LOCK){
+ if (systemCapabilityType != null && listener != null) {
+ if (onSystemCapabilityListeners.get(systemCapabilityType) == null) {
+ onSystemCapabilityListeners.put(systemCapabilityType, new CopyOnWriteArrayList<OnSystemCapabilityListener>());
+ }
+ onSystemCapabilityListeners.get(systemCapabilityType).add(listener);
+ }
+ }
+ }
+
+ /**
+ * Remove an OnSystemCapabilityListener that was previously added
+ * @param systemCapabilityType Type of capability
+ * @param listener the listener that should be removed
+ */
+ public boolean removeOnSystemCapabilityListener(final SystemCapabilityType systemCapabilityType, final OnSystemCapabilityListener listener){
+ synchronized(LISTENER_LOCK){
+ if(onSystemCapabilityListeners!= null
+ && systemCapabilityType != null
+ && listener != null
+ && onSystemCapabilityListeners.get(systemCapabilityType) != null){
+ return onSystemCapabilityListeners.get(systemCapabilityType).remove(listener);
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param systemCapabilityType Type of capability desired
+ * passes GetSystemCapabilityType request to `callback` to be sent by proxy
+ */
+ private void retrieveCapability(final SystemCapabilityType systemCapabilityType, final OnSystemCapabilityListener scListener){
+ final GetSystemCapability request = new GetSystemCapability();
+ request.setSystemCapabilityType(systemCapabilityType);
+ request.setOnRPCResponseListener(new OnRPCResponseListener() {
+ @Override
+ public void onResponse(int correlationId, RPCResponse response) {
+ if(response.getSuccess()){
+ Object retrievedCapability = ((GetSystemCapabilityResponse) response).getSystemCapability().getCapabilityForType(systemCapabilityType);
+ cachedSystemCapabilities.put(systemCapabilityType, retrievedCapability);
+ if(scListener!=null){scListener.onCapabilityRetrieved(retrievedCapability); }
+ }else{
+ if(scListener!=null){scListener.onError(response.getInfo());}
+ }
+ }
+
+ @Override
+ public void onError(int correlationId, Result resultCode, String info) {
+ if(scListener!=null){scListener.onError(info);}
+ }
+ });
+ request.setCorrelationID(CorrelationIdGenerator.generateId());
+
+ if(callback!=null){
+ callback.sendRPCRequest(request);
+ }
+ }
+
+ /**
+ * Converts a capability object into a list.
+ * @param object the capability that needs to be converted
+ * @param classType The class type of that should be contained in the list
+ * @return a List of capabilities if object is instance of List, otherwise it will return null.
+ */
+ @SuppressWarnings({"unchecked"})
+ public static <T> List<T> convertToList(Object object, Class<T> classType){
+ if(classType!=null && object!=null && object instanceof List ){
+ List list = (List)object;
+ if(!list.isEmpty()){
+ if(classType.isInstance(list.get(0))){
+ return (List<T>)object;
+ }else{
+ //The list is not of the correct list type
+ return null;
+ }
+ }else {
+ //We return a new list of type T instead of null because while we don't know if
+ //the original list was of type T we want to ensure that we don't throw a cast class exception
+ //but still
+ return new ArrayList<T>();
+ }
+ }else{
+ return null;
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/TTSChunkFactory.java b/base/src/main/java/com/smartdevicelink/proxy/TTSChunkFactory.java
new file mode 100644
index 000000000..75305bbd0
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/TTSChunkFactory.java
@@ -0,0 +1,39 @@
+package com.smartdevicelink.proxy;
+
+import java.util.Vector;
+
+import com.smartdevicelink.proxy.rpc.TTSChunk;
+import com.smartdevicelink.proxy.rpc.enums.SpeechCapabilities;
+
+public class TTSChunkFactory {
+
+ public static TTSChunk createChunk(SpeechCapabilities type, String text) {
+ TTSChunk ret = new TTSChunk();
+ ret.setType(type);
+ ret.setText(text);
+ return ret;
+ }
+
+ public static Vector<TTSChunk> createSimpleTTSChunks(String simple) {
+ if (simple == null) {
+ return null;
+ }
+
+ Vector<TTSChunk> chunks = new Vector<TTSChunk>();
+
+ TTSChunk chunk = createChunk(SpeechCapabilities.TEXT, simple);
+ chunks.add(chunk);
+ return chunks;
+ }
+
+ public static Vector<TTSChunk> createPrerecordedTTSChunks(String prerecorded) {
+ if (prerecorded == null) {
+ return null;
+ }
+
+ Vector<TTSChunk> chunks = new Vector<TTSChunk>();
+ TTSChunk chunk = createChunk(SpeechCapabilities.PRE_RECORDED, prerecorded);
+ chunks.add(chunk);
+ return chunks;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/callbacks/InternalProxyMessage.java b/base/src/main/java/com/smartdevicelink/proxy/callbacks/InternalProxyMessage.java
new file mode 100644
index 000000000..1f6598f23
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/callbacks/InternalProxyMessage.java
@@ -0,0 +1,19 @@
+package com.smartdevicelink.proxy.callbacks;
+
+public class InternalProxyMessage {
+ private String _functionName;
+ public static final String OnProxyError = "OnProxyError";
+ public static final String OnProxyOpened = "OnProxyOpened";
+ public static final String OnProxyClosed = "OnProxyClosed";
+ public static final String OnServiceEnded = "OnServiceEnded";
+ public static final String OnServiceNACKed = "OnServiceNACKed";
+
+ public InternalProxyMessage(String functionName) {
+ //this(functionName, null, null);
+ this._functionName = functionName;
+ }
+
+ public String getFunctionName() {
+ return _functionName;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnError.java b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnError.java
new file mode 100644
index 000000000..e93fc684c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnError.java
@@ -0,0 +1,25 @@
+package com.smartdevicelink.proxy.callbacks;
+
+public class OnError extends InternalProxyMessage {
+
+ private String _info;
+ private Exception _e;
+
+ public OnError() {
+ super(InternalProxyMessage.OnProxyError);
+ }
+
+ public OnError(String info, Exception e) {
+ super(InternalProxyMessage.OnProxyError);
+ this._info = info;
+ this._e = e;
+ }
+
+ public String getInfo() {
+ return _info;
+ }
+
+ public Exception getException() {
+ return _e;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyClosed.java b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyClosed.java
new file mode 100644
index 000000000..2e82ee325
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyClosed.java
@@ -0,0 +1,33 @@
+package com.smartdevicelink.proxy.callbacks;
+
+import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason;
+
+public class OnProxyClosed extends InternalProxyMessage {
+
+ private String _info;
+ private Exception _e;
+ private SdlDisconnectedReason _reason;
+
+ public OnProxyClosed() {
+ super(InternalProxyMessage.OnProxyClosed);
+ }
+
+ public OnProxyClosed(String info, Exception e, SdlDisconnectedReason reason) {
+ super(InternalProxyMessage.OnProxyClosed);
+ this._info = info;
+ this._e = e;
+ this._reason = reason;
+ }
+
+ public String getInfo() {
+ return _info;
+ }
+
+ public SdlDisconnectedReason getReason() {
+ return _reason;
+ }
+
+ public Exception getException() {
+ return _e;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyOpened.java b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyOpened.java
new file mode 100644
index 000000000..82a1ed706
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnProxyOpened.java
@@ -0,0 +1,8 @@
+package com.smartdevicelink.proxy.callbacks;
+
+public class OnProxyOpened extends InternalProxyMessage {
+
+ public OnProxyOpened() {
+ super(InternalProxyMessage.OnProxyOpened);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceEnded.java b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceEnded.java
new file mode 100644
index 000000000..d69802e0f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceEnded.java
@@ -0,0 +1,21 @@
+package com.smartdevicelink.proxy.callbacks;
+
+import com.smartdevicelink.protocol.enums.SessionType;
+
+public class OnServiceEnded extends InternalProxyMessage {
+ private SessionType sessionType;
+
+ public OnServiceEnded() {
+ super(InternalProxyMessage.OnServiceEnded);
+ }
+
+ public OnServiceEnded(SessionType sessionType) {
+ super(InternalProxyMessage.OnServiceEnded);
+ this.sessionType = sessionType;
+ }
+
+ public SessionType getSessionType() {
+ return this.sessionType;
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceNACKed.java b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceNACKed.java
new file mode 100644
index 000000000..9eae5e4c4
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/callbacks/OnServiceNACKed.java
@@ -0,0 +1,21 @@
+package com.smartdevicelink.proxy.callbacks;
+
+import com.smartdevicelink.protocol.enums.SessionType;
+
+public class OnServiceNACKed extends InternalProxyMessage {
+ private SessionType sessionType;
+
+ public OnServiceNACKed() {
+ super(InternalProxyMessage.OnServiceNACKed);
+ }
+
+ public OnServiceNACKed(SessionType sessionType) {
+ super(InternalProxyMessage.OnServiceNACKed);
+ this.sessionType = sessionType;
+ }
+
+ public SessionType getSessionType() {
+ return this.sessionType;
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/constants/Jingles.java b/base/src/main/java/com/smartdevicelink/proxy/constants/Jingles.java
new file mode 100644
index 000000000..a47572317
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/constants/Jingles.java
@@ -0,0 +1,15 @@
+package com.smartdevicelink.proxy.constants;
+
+@Deprecated
+public class Jingles {
+ public static final String POSITIVE_JINGLE = "POSITIVE_JINGLE";
+
+ public static final String NEGATIVE_JINGLE = "NEGATIVE_JINGLE";
+
+ public static final String INITIAL_JINGLE = "INITIAL_JINGLE";
+
+ public static final String LISTEN_JINGLE = "LISTEN_JINGLE";
+
+ public static final String HELP_JINGLE = "HELP_JINGLE";
+
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/proxy/constants/Names.java b/base/src/main/java/com/smartdevicelink/proxy/constants/Names.java
new file mode 100644
index 000000000..f578dce18
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/constants/Names.java
@@ -0,0 +1,506 @@
+package com.smartdevicelink.proxy.constants;
+
+@Deprecated
+public class Names {
+ public static final String request = "request";
+ public static final String response = "response";
+ public static final String notification = "notification";
+ public static final String function_name = "name";
+ public static final String parameters = "parameters";
+ public static final String bulkData = "bulkData";
+
+ public static final String RegisterAppInterface = "RegisterAppInterface";
+ public static final String UnregisterAppInterface = "UnregisterAppInterface";
+ public static final String Alert = "Alert";
+ public static final String Show = "Show";
+ public static final String Speak = "Speak";
+ public static final String AddCommand = "AddCommand";
+ public static final String DeleteCommand = "DeleteCommand";
+ public static final String AddSubMenu = "AddSubMenu";
+ public static final String DeleteSubMenu = "DeleteSubMenu";
+ public static final String CreateInteractionChoiceSet = "CreateInteractionChoiceSet";
+ public static final String DeleteInteractionChoiceSet = "DeleteInteractionChoiceSet";
+ public static final String PerformInteraction = "PerformInteraction";
+ public static final String DialNumber = "DialNumber";
+ public static final String EncodedSyncPData = "EncodedSyncPData";
+ public static final String SyncPData = "SyncPData";
+ public static final String SubscribeButton = "SubscribeButton";
+ public static final String UnsubscribeButton = "UnsubscribeButton";
+ public static final String SubscribeVehicleData = "SubscribeVehicleData";
+ public static final String UnsubscribeVehicleData = "UnsubscribeVehicleData";
+ public static final String SetMediaClockTimer = "SetMediaClockTimer";
+ public static final String SetGlobalProperties = "SetGlobalProperties";
+ public static final String GenericResponse = "GenericResponse";
+ public static final String ScrollableMessage = "ScrollableMessage";
+ public static final String GetDID = "GetDID";
+ public static final String GetDTCs = "GetDTCs";
+ public static final String DiagnosticMessage = "DiagnosticMessage";
+ public static final String SystemRequest = "SystemRequest";
+ public static final String ReadDID = "ReadDID";
+ public static final String OnVehicleData = "OnVehicleData";
+ public static final String GetFile = "GetFile";
+ public static final String PutFile = "PutFile";
+ public static final String DeleteFile = "DeleteFile";
+ public static final String ListFiles = "ListFiles";
+ public static final String EndAudioCapture = "EndAudioCapture";
+ public static final String GetVehicleData = "GetVehicleData";
+ public static final String ResetGlobalProperties = "ResetGlobalProperties";
+ public static final String PerformAudioCapture = "PerformAudioCapture";
+ public static final String SetAppIcon = "SetAppIcon";
+ public static final String ChangeRegistration = "ChangeRegistration";
+ public static final String SetDisplayLayout = "SetDisplayLayout";
+ public static final String keypressMode = "keypressMode";
+ public static final String keyboardLayout = "keyboardLayout";
+ public static final String limitedCharacterList = "limitedCharacterList";
+ public static final String autoCompleteText = "autoCompleteText";
+ public static final String OnLanguageChange = "OnLanguageChange";
+ public static final String hmiDisplayLanguage = "hmiDisplayLanguage";
+ public static final String displayLayout = "displayLayout";
+ public static final String ttsName = "ttsName";
+ public static final String hmiDisplayLanguageDesired = "hmiDisplayLanguageDesired";
+ public static final String appHMIType = "appHMIType";
+ public static final String hashID = "hashID";
+ public static final String appID = "appID";
+ public static final String vrHelpTitle = "vrHelpTitle";
+ public static final String graphic = "graphic";
+ public static final String customPresets = "customPresets";
+ public static final String softButtonCapabilities = "softButtonCapabilities";
+ public static final String presetBankCapabilities = "presetBankCapabilities";
+ public static final String vehicleType = "vehicleType";
+ public static final String make = "make";
+ public static final String model = "model";
+ public static final String modelYear = "modelYear";
+ public static final String trim = "trim";
+ public static final String allowed = "allowed";
+ public static final String userDisallowed = "userDisallowed";
+ public static final String rpcName = "rpcName";
+ public static final String hmiPermissions = "hmiPermissions";
+ public static final String parameterPermissions = "parameterPermissions";
+ public static final String permissionItem = "permissionItem";
+ public static final String numTicks = "numTicks";
+ public static final String sliderHeader = "sliderHeader";
+ public static final String sliderFooter = "sliderFooter";
+ public static final String PerformAudioPassThru = "PerformAudioPassThru";
+ public static final String PerformAudioPassThruResponse = "PerformAudioPassThruResponse";
+ public static final String EndAudioPassThru = "EndAudioPassThru";
+ public static final String EndAudioPassThruResponse = "EndAudioPassThruResponse";
+ public static final String OnAudioPassThru = "OnAudioPassThru";
+ public static final String ShowConstantTBT = "ShowConstantTBT";
+ public static final String AlertManeuver = "AlertManeuver";
+ public static final String UpdateTurnList = "UpdateTurnList";
+
+ public static final String OnCommand = "OnCommand";
+ public static final String OnDataPublished = "OnDataPublished";
+ public static final String OnButtonPress = "OnButtonPress";
+ public static final String OnButtonEvent = "OnButtonEvent";
+ public static final String OnHMIStatus = "OnHMIStatus";
+ public static final String OnTBTClientState = "OnTBTClientState";
+ public static final String OnEncodedSyncPData = "OnEncodedSyncPData";
+ public static final String onEncodedSyncPDataResponse = "onEncodedSyncPDataResponse";
+ public static final String OnSyncPData = "OnSyncPData";
+ public static final String onOnSyncPData = "onOnSyncPData";
+ public static final String OnDriverDistraction = "OnDriverDistraction";
+ public static final String OnAppInterfaceUnregistered = "OnAppInterfaceUnregistered";
+ public static final String OnKeyboardInput = "OnKeyboardInput";
+ public static final String OnTouchEvent = "OnTouchEvent";
+ public static final String OnSystemRequest = "OnSystemRequest";
+ public static final String OnHashChange = "OnHashChange";
+ public static final String OnProxyClosed = "OnProxyClosed";
+ public static final String OnProxyError = "OnProxyError";
+ public static final String OnProxyOpened = "OnProxyOpened";
+ public static final String OnProxyUnusable = "OnProxyUnusable";
+ public static final String OnHMILevelChange = "OnHMILevelChange";
+ public static final String OnSdlChoiceChosen = "OnSdlChoiceChosen";
+ public static final String OnPermissionsChange = "OnPermissionsChange";
+ public static final String OnScreenPresetsAvailable = "OnScreenPresetsAvailable";
+ public static final String isHighlighted = "isHighlighted";
+ public static final String softButtonID = "softButtonID";
+ public static final String fileType = "fileType";
+ public static final String url = "url";
+ public static final String requestType = "requestType";
+ public static final String fileName = "fileName";
+ public static final String persistentFile = "persistentFile";
+ public static final String spaceAvailable = "spaceAvailable";
+ public static final String filenames = "filenames";
+ public static final String cmdIcon = "cmdIcon";
+ public static final String Slider = "Slider";
+ public static final String sliderPosition = "sliderPosition";
+ public static final String samplingRate = "samplingRate";
+ public static final String audioType = "audioType";
+ public static final String satRadioESN = "satRadioESN";
+ public static final String dtc = "dtc";
+ public static final String tryAgainTime = "tryAgainTime";
+
+ public static final String success = "success";
+ public static final String resultCode = "resultCode";
+ public static final String info = "info";
+
+ public static final String payload = "payload";
+ public static final String reason = "reason";
+ public static final String state = "state";
+ public static final String cmdID = "cmdID";
+ public static final String menuParams = "menuParams";
+ public static final String parentID = "parentID";
+ public static final String position = "position";
+ public static final String menuName = "menuName";
+ public static final String vrCommands = "vrCommands";
+ public static final String language = "language";
+ public static final String languageDesired = "languageDesired";
+ public static final String triggerSource = "triggerSource";
+ public static final String subscriptionType = "subscriptionType";
+ public static final String data = "data";
+ public static final String event = "event";
+ public static final String correlationID = "correlationID";
+ public static final String sdlMsgVersion = "syncMsgVersion";
+ public static final String deviceInfo = "deviceInfo";
+ public static final String majorVersion = "majorVersion";
+ public static final String minorVersion = "minorVersion";
+ public static final String appName = "appName";
+ public static final String ngnMediaScreenAppName = "ngnMediaScreenAppName";
+ public static final String isMediaApplication = "isMediaApplication";
+ public static final String vrSynonyms = "vrSynonyms";
+ public static final String usesVehicleData = "usesVehicleData";
+ public static final String text = "text";
+ public static final String type = "type";
+ public static final String ttsChunks = "ttsChunks";
+ public static final String playTone = "playTone";
+ public static final String duration = "duration";
+ public static final String mainField1 = "mainField1";
+ public static final String mainField2 = "mainField2";
+ public static final String mainField3 = "mainField3";
+ public static final String mainField4 = "mainField4";
+ public static final String statusBar = "statusBar";
+ public static final String name = "name";
+ public static final String menuID = "menuID";
+ public static final String longPress = "longPress";
+ public static final String shortPress = "shortPress";
+ public static final String buttonName = "buttonName";
+ public static final String buttonPressMode = "buttonPressMode";
+ public static final String buttonEventMode = "buttonEventMode";
+ public static final String minutes = "minutes";
+ public static final String seconds = "seconds";
+ public static final String startTime = "startTime";
+ public static final String endTime = "endTime";
+ public static final String updateMode = "updateMode";
+ public static final String mediaClock = "mediaClock";
+ public static final String initialText = "initialText";
+ public static final String initialPrompt = "initialPrompt";
+ public static final String helpPrompt = "helpPrompt";
+ public static final String timeoutPrompt = "timeoutPrompt";
+ public static final String timeout = "timeout";
+ public static final String choiceSet = "choiceSet";
+ public static final String interactionMode = "interactionMode";
+ public static final String result = "result";
+ public static final String alertText1 = "alertText1";
+ public static final String alertText2 = "alertText2";
+ public static final String alertText3 = "alertText3";
+ public static final String shortPressAvailable = "shortPressAvailable";
+ public static final String longPressAvailable = "longPressAvailable";
+ public static final String upDownAvailable = "upDownAvailable";
+ public static final String width = "width";
+ public static final String height = "height";
+ public static final String resolutionWidth = "resolutionWidth";
+ public static final String resolutionHeight = "resolutionHeight";
+ public static final String characterSet = "characterSet";
+ public static final String displayType = "displayType";
+ public static final String mediaClockFormats = "mediaClockFormats";
+ public static final String textFields = "textFields";
+ public static final String imageFields = "imageFields";
+ public static final String autoActivateID = "autoActivateID";
+ public static final String vehicleDataCapabilities = "vehicleDataCapabilities";
+ public static final String speechCapabilities = "speechCapabilities";
+ public static final String vrCapabilities = "vrCapabilities";
+ public static final String audioPassThruCapabilities = "audioPassThruCapabilities";
+ public static final String buttonCapabilities = "buttonCapabilities";
+ public static final String displayCapabilities = "displayCapabilities";
+ public static final String hmiZoneCapabilities = "hmiZoneCapabilities";
+ public static final String interactionChoiceSetID = "interactionChoiceSetID";
+ public static final String interactionChoiceSetIDList = "interactionChoiceSetIDList";
+ public static final String audioFileName = "audioFileName";
+ public static final String gpsPositionValid = "gpsPositionValid";
+ public static final String longitudeDegrees = "longitudeDegrees";
+ public static final String latitudeDegrees = "latitudeDegrees";
+ public static final String utcYear = "utcYear";
+ public static final String utcMonth = "utcMonth";
+ public static final String utcDay = "utcDay";
+ public static final String utcHours = "utcHours";
+ public static final String utcMinutes = "utcMinutes";
+ public static final String utcSeconds = "utcSeconds";
+ public static final String compassDirection = "compassDirection";
+ public static final String pdop = "pdop";
+ public static final String vdop = "vdop";
+ public static final String hdop = "hdop";
+ public static final String actual = "actual";
+ public static final String satellites = "satellites";
+ public static final String dimension = "dimension";
+ public static final String altitude = "altitude";
+ public static final String heading = "heading";
+ public static final String speed = "speed";
+ public static final String number = "number";
+ public static final String smartDeviceLinkFileName = "syncFileName";
+ public static final String localFileName = "localFileName";
+ public static final String maxDuration = "maxDuration";
+ public static final String timerMode = "timerMode";
+ public static final String status = "status";
+ public static final String pressure = "pressure";
+ public static final String hours = "hours";
+ public static final String rows = "rows";
+ public static final String pressureTellTale = "pressureTellTale";
+ public static final String leftFront = "leftFront";
+ public static final String rightFront = "rightFront";
+ public static final String leftRear = "leftRear";
+ public static final String rightRear = "rightRear";
+ public static final String innerLeftRear = "innerLeftRear";
+ public static final String innerRightRear = "innerRightRear";
+ public static final String VehicleData = "VehicleData";
+ public static final String alignment = "alignment";
+ public static final String mediaTrack = "mediaTrack";
+ public static final String properties = "properties";
+ public static final String choiceID = "choiceID";
+ public static final String bitsPerSample = "bitsPerSample";
+ public static final String hmiLevel = "hmiLevel";
+ public static final String audioStreamingState = "audioStreamingState";
+ public static final String systemContext = "systemContext";
+ public static final String sdlChoice = "sdlChoice";
+ public static final String sdlCommand = "sdlCommand";
+ public static final String URL = "URL";
+ public static final String Timeout = "Timeout";
+ public static final String PermissionGroupName = "PermissionGroupName";
+ public static final String PermissionGroupStatus = "PermissionGroupStatus";
+ public static final String PermissionGroupItems = "PermissionGroupItems";
+ public static final String audioPacket = "audioPacket";
+ public static final String audioPassThruDisplayText1 = "audioPassThruDisplayText1";
+ public static final String audioPassThruDisplayText2 = "audioPassThruDisplayText2";
+ public static final String bitRate = "bitRate";
+ public static final String rpm = "rpm";
+ public static final String fuelLevel = "fuelLevel";
+ public static final String avgFuelEconomy = "avgFuelEconomy";
+ public static final String batteryVoltage = "batteryVoltage";
+ public static final String externalTemperature = "externalTemperature";
+ public static final String vin = "vin";
+ public static final String prndl = "prndl";
+ public static final String tirePressure = "tirePressure";
+ public static final String batteryPackVoltage = "batteryPackVoltage";
+ public static final String batteryPackCurrent = "batteryPackCurrent";
+ public static final String batteryPackTemperature = "batteryPackTemperature";
+ public static final String engineTorque = "engineTorque";
+ public static final String odometer = "odometer";
+ public static final String tripOdometer = "tripOdometer";
+ public static final String genericbinary = "genericbinary";
+ public static final String GPSData = "GPSData";
+ public static final String gps = "gps";
+ public static final String fuelLevel_State = "fuelLevel_State";
+ public static final String instantFuelConsumption = "instantFuelConsumption";
+ public static final String beltStatus = "beltStatus";
+ public static final String bodyInformation = "bodyInformation";
+ public static final String deviceStatus = "deviceStatus";
+ public static final String driverBraking = "driverBraking";
+ public static final String wiperStatus = "wiperStatus";
+ public static final String fuelEconomy = "fuelEconomy";
+ public static final String engineOilLife = "engineOilLife";
+ public static final String headLampStatus = "headLampStatus";
+ public static final String brakeTorque = "brakeTorque";
+ public static final String turboBoost = "turboBoost";
+ public static final String coolantTemp = "coolantTemp";
+ public static final String airFuelRatio = "airFuelRatio";
+ public static final String coolingHeadTemp = "coolingHeadTemp";
+ public static final String oilTemp = "oilTemp";
+ public static final String intakeAirTemp = "intakeAirTemp";
+ public static final String gearShiftAdvice = "gearShiftAdvice";
+ public static final String acceleration = "acceleration";
+ public static final String accPedalPosition = "accPedalPosition";
+ public static final String clutchPedalPosition = "clutchPedalPosition";
+ public static final String reverseGearStatus = "reverseGearStatus";
+ public static final String accTorque = "accTorque";
+ public static final String ambientLightStatus = "ambientLightStatus";
+ public static final String ambientLightSensorStatus = "ambientLightSensorStatus";
+ public static final String dataType = "dataType";
+ public static final String identifier = "identifier";
+ public static final String statusByte = "statusByte";
+ public static final String didResult = "didResult";
+ public static final String ecuName = "ecuName";
+ public static final String didLocation = "didLocation";
+ public static final String value = "value";
+ public static final String softButtonName = "softButtonName";
+ public static final String imageSupported = "imageSupported";
+ public static final String systemAction = "systemAction";
+ public static final String image = "image";
+ public static final String secondaryText = "secondaryText";
+ public static final String tertiaryText = "tertiaryText";
+ public static final String secondaryImage = "secondaryImage";
+ public static final String imageType = "imageType";
+ public static final String fileData = "fileData";
+ public static final String scrollableMessageBody = "scrollableMessageBody";
+ public static final String softButtons = "softButtons";
+ public static final String customButtonID = "customButtonID";
+ public static final String vrHelp = "vrHelp";
+ public static final String interactionLayout = "interactionLayout";
+ public static final String customButtonName = "customButtonName";
+ public static final String navigationText = "navigationText";
+ public static final String turnIcon = "turnIcon";
+ public static final String nextTurnIcon = "nextTurnIcon";
+ public static final String navigationText1 = "navigationText1";
+ public static final String navigationText2 = "navigationText2";
+ public static final String eta = "eta";
+ public static final String totalDistance = "totalDistance";
+ public static final String distanceToManeuver = "distanceToManeuver";
+ public static final String distanceToManeuverScale = "distanceToManeuverScale";
+ public static final String maneuverComplete = "maneuverComplete";
+ public static final String turnList = "turnList";
+ public static final String steeringWheelAngle = "steeringWheelAngle";
+ public static final String menuTitle = "menuTitle";
+ public static final String menuIcon = "menuIcon";
+ public static final String keyboardProperties = "keyboardProperties";
+ public static final String driverBeltDeployed = "driverBeltDeployed";
+ public static final String passengerBeltDeployed = "passengerBeltDeployed";
+ public static final String passengerBuckleBelted = "passengerBuckleBelted";
+ public static final String driverBuckleBelted = "driverBuckleBelted";
+ public static final String leftRow2BuckleBelted = "leftRow2BuckleBelted";
+ public static final String passengerChildDetected = "passengerChildDetected";
+ public static final String rightRow2BuckleBelted = "rightRow2BuckleBelted";
+ public static final String middleRow2BuckleBelted = "middleRow2BuckleBelted";
+ public static final String middleRow3BuckleBelted = "middleRow3BuckleBelted";
+ public static final String leftRow3BuckleBelted = "leftRow3BuckleBelted";
+ public static final String rightRow3BuckleBelted = "rightRow3BuckleBelted";
+ public static final String rearInflatableBelted = "rearInflatableBelted";
+ public static final String leftRearInflatableBelted = "leftRearInflatableBelted";
+ public static final String rightRearInflatableBelted = "rightRearInflatableBelted";
+ public static final String middleRow1BeltDeployed = "middleRow1BeltDeployed";
+ public static final String middleRow1BuckleBelted = "middleRow1BuckleBelted";
+
+ public static final String graphicSupported = "graphicSupported";
+ public static final String screenParams = "screenParams";
+ public static final String muteAudio = "muteAudio";
+ public static final String parkBrakeActive = "parkBrakeActive";
+ public static final String ignitionStableStatus = "ignitionStableStatus";
+ public static final String ignitionStatus = "ignitionStatus";
+ public static final String driverDoorAjar = "driverDoorAjar";
+ public static final String passengerDoorAjar = "passengerDoorAjar";
+ public static final String rearLeftDoorAjar = "rearLeftDoorAjar";
+ public static final String rearRightDoorAjar = "rearRightDoorAjar";
+ public static final String systemFile = "systemFile";
+
+ public static final String voiceRecOn = "voiceRecOn";
+ public static final String btIconOn = "btIconOn";
+ public static final String callActive = "callActive";
+ public static final String phoneRoaming = "phoneRoaming";
+ public static final String textMsgAvailable = "textMsgAvailable";
+ public static final String battLevelStatus = "battLevelStatus";
+ public static final String stereoAudioOutputMuted = "stereoAudioOutputMuted";
+ public static final String monoAudioOutputMuted = "monoAudioOutputMuted";
+ public static final String signalLevelStatus = "signalLevelStatus";
+ public static final String primaryAudioSource = "primaryAudioSource";
+ public static final String eCallEventActive = "eCallEventActive";
+
+ public static final String fuelEconomySinceLastReset = "fuelEconomySinceLastReset";
+ public static final String currentTripFuelEconomy = "currentTripFuelEconomy";
+ public static final String averageTripFuelEconomy = "averageTripFuelEconomy";
+ public static final String currentCycleFuelEconomy = "currentCycleFuelEconomy";
+
+ public static final String lightSwitchStatus = "lightSwitchStatus";
+ public static final String highBeamsOn = "highBeamsOn";
+ public static final String lowBeamsOn = "lowBeamsOn";
+
+ public static final String electricFuelConsumption = "electricFuelConsumption";
+ public static final String stateOfCharge = "stateOfCharge";
+ public static final String fuelMaintenanceMode = "fuelMaintenanceMode";
+ public static final String distanceToEmpty = "distanceToEmpty";
+
+ public static final String dtcMask = "dtcMask";
+ public static final String targetID = "targetID";
+ public static final String messageLength = "messageLength";
+ public static final String messageData = "messageData";
+ public static final String messageDataResult = "messageDataResult";
+
+ public static final String imageTypeSupported = "imageTypeSupported";
+ public static final String imageResolution = "imageResolution";
+ public static final String x = "x";
+ public static final String y = "y";
+ public static final String id = "id";
+ public static final String ts = "ts";
+ public static final String c = "c";
+ public static final String resolution = "resolution";
+ public static final String touchEventAvailable = "touchEventAvailable";
+
+ public static final String pressAvailable = "pressAvailable";
+ public static final String multiTouchAvailable = "multiTouchAvailable";
+ public static final String doublePressAvailable = "doublePressAvailable";
+ public static final String templatesAvailable = "templatesAvailable";
+ public static final String numCustomPresetsAvailable = "numCustomPresetsAvailable";
+ public static final String prerecordedSpeech = "prerecordedSpeech";
+ public static final String manualTextEntry = "manualTextEntry";
+ public static final String progressIndicator = "progressIndicator";
+ public static final String secondaryGraphic = "secondaryGraphic";
+ public static final String offset = "offset";
+ public static final String length = "length";
+
+ public static final String hardware = "hardware";
+ public static final String firmwareRev = "firmwareRev";
+ public static final String os = "os";
+ public static final String osVersion = "osVersion";
+ public static final String carrier = "carrier";
+ public static final String maxNumberRFCOMMPorts = "maxNumberRFCOMMPorts";
+
+ public static final String onReadDIDResponse = "onReadDIDResponse";
+ public static final String onGetDTCsResponse = "onGetDTCsResponse";
+ public static final String onOnKeyboardInput = "onOnKeyboardInput";
+ public static final String onOnTouchEvent = "onOnTouchEvent";
+ public static final String onOnSystemRequest = "onOnSystemRequest";
+
+ public static final String onDiagnosticMessageResponse = "onDiagnosticMessageResponse";
+ public static final String onSystemRequestResponse = "onSystemRequestResponse";
+ public static final String onGetVehicleDataResponse = "onGetVehicleDataResponse";
+ public static final String getSupportedDiagModes = "getSupportedDiagModes";
+ public static final String supportedDiagModes = "supportedDiagModes";
+
+ public static final String driverAirbagDeployed = "driverAirbagDeployed";
+ public static final String driverSideAirbagDeployed = "driverSideAirbagDeployed";
+ public static final String driverCurtainAirbagDeployed = "driverCurtainAirbagDeployed";
+ public static final String passengerAirbagDeployed = "passengerAirbagDeployed";
+ public static final String passengerCurtainAirbagDeployed = "passengerCurtainAirbagDeployed";
+ public static final String driverKneeAirbagDeployed = "driverKneeAirbagDeployed";
+ public static final String passengerSideAirbagDeployed = "passengerSideAirbagDeployed";
+ public static final String passengerKneeAirbagDeployed = "passengerKneeAirbagDeployed";
+
+ public static final String powerModeActive = "powerModeActive";
+ public static final String powerModeQualificationStatus = "powerModeQualificationStatus";
+ public static final String carModeStatus = "carModeStatus";
+ public static final String powerModeStatus = "powerModeStatus";
+
+ public static final String eCallNotificationStatus = "eCallNotificationStatus";
+ public static final String auxECallNotificationStatus = "auxECallNotificationStatus";
+ public static final String eCallConfirmationStatus = "eCallConfirmationStatus";
+ public static final String e911Override = "e911Override";
+
+ public static final String emergencyEventType = "emergencyEventType";
+ public static final String fuelCutoffStatus = "fuelCutoffStatus";
+ public static final String rolloverEvent = "rolloverEvent";
+ public static final String maximumChangeVelocity = "maximumChangeVelocity";
+ public static final String multipleEvents = "multipleEvents";
+
+ public static final String eCallInfo = "eCallInfo";
+ public static final String airbagStatus = "airbagStatus";
+ public static final String emergencyEvent = "emergencyEvent";
+ public static final String clusterModeStatus = "clusterModeStatus";
+ public static final String myKey = "myKey";
+ public static final String timeToDestination = "timeToDestination";
+
+ public static final String driverDistraction = "driverDistraction";
+ public static final String showLockScreen = "showLockScreen";
+ public static final String userSelected = "userSelected";
+ public static final String notSet = "notSet";
+
+ public static final String headers = "headers";
+ public static final String body = "body";
+
+ public static final String ContentType = "ContentType";
+ public static final String ConnectTimeout = "ConnectTimeout";
+ public static final String DoOutput = "DoOutput";
+ public static final String DoInput = "DoInput";
+ public static final String UseCaches = "UseCaches";
+ public static final String RequestMethod = "RequestMethod";
+ public static final String ReadTimeout = "ReadTimeout";
+ public static final String InstanceFollowRedirects = "InstanceFollowRedirects";
+ public static final String charset = "charset";
+ public static final String ContentLength = "Content-Length";
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/IAudioStreamListener.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IAudioStreamListener.java
new file mode 100644
index 000000000..8d21b7615
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IAudioStreamListener.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2017, Xevo Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.proxy.interfaces;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A listener that receives audio streaming data from app.
+ */
+public interface IAudioStreamListener {
+ /**
+ * Sends a chunk of audio data to SDL Core.
+ * <p>
+ * Note: this method must not be called after SdlProxyBase.endAudioStream() is called.
+ *
+ * @param data Byte array containing audio data
+ * @param offset Starting offset in 'data'
+ * @param length Length of the data
+ * @param presentationTimeUs (Reserved for future use) Presentation timestamp (PTS) of the
+ * last audio sample data included in this chunk, in microseconds.
+ * It must be greater than the previous timestamp.
+ * Specify -1 if unknown.
+ * @throws ArrayIndexOutOfBoundsException When offset does not satisfy
+ * {@code 0 <= offset && offset <= data.length}
+ * or length does not satisfy
+ * {@code 0 < length && offset + length <= data.length}
+ */
+ void sendAudio(byte[] data, int offset, int length, long presentationTimeUs)
+ throws ArrayIndexOutOfBoundsException;
+
+ /**
+ * Sends a chunk of audio data to SDL Core.
+ * <p>
+ * Note: this method must not be called after SdlProxyBase.endAudioStream() is called.
+ *
+ * @param data Data chunk to send. Its position will be updated upon return.
+ * @param presentationTimeUs (Reserved for future use) Presentation timestamp (PTS) of the
+ * last audio sample data included in this chunk, in microseconds.
+ * It must be greater than the previous timestamp.
+ * Specify -1 if unknown.
+ */
+ void sendAudio(ByteBuffer data, long presentationTimeUs);
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerALM.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerALM.java
new file mode 100644
index 000000000..980ded6df
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerALM.java
@@ -0,0 +1,48 @@
+package com.smartdevicelink.proxy.interfaces;
+
+public interface IProxyListenerALM extends IProxyListenerBase {
+ // Adds Advanced Life-cycle Management call-backs to the IProxyListenerAbstract interface
+
+ /**
+ * **MOVED TO IProxyListenerBase** - onOnHMIStatus() being called indicates that the proxy has entered a state in which the
+ * application may create SDL related resources (addCommands, ChoiceSets).
+ */
+ //public void onOnHMIStatus(OnHMIStatus notification);
+
+ /**
+ * **MOVED TO IProxyListenerBase** - onProxyClosed() being called indicates that the app is no longer registered with SDL
+ * All resources on SDL (addCommands and ChoiceSets) have been deleted and will have to be
+ * recreated upon the next onReadyForInitialization() call-back.
+ */
+ //public void onProxyClosed(String info, Exception e);
+
+ /**
+ * **MOVED TO IProxyListenerBase** - onError() being called indicates that the proxy has experienced an unrecoverable error.
+ * A new proxy object must be initiated to reestablish connection with SDL.
+ *
+ * @param info - Any info present about the error that occurred.
+ * @param e - Any exception thrown by the error.
+ */
+ //public void onError(String info, Exception e);
+
+ /**
+ * **Deprecated** - onSdlInterfaceAvailable() being called indicates that the proxy now has access to SDL's HMI.
+ * Monitor the onFocusChange call-back to determine which level of HMI is available to the proxy.
+ *
+ * @param isFirstAvailability - Indicates this is the first onSdlInterfaceAvailable in this lifecycle.
+ */
+ // HMI (Background, Limited, Full) from Unavailable = onSdlInterfaceAvailable(Boolean isFirstAvailability);
+
+ /**
+ * **Deprecated** - onSdlInterfaceUnavailable() being called indicates that the proxy does NOT have access to SDL's HIM.
+ */
+ // HMI None onSdlInterfaceUnavailable();
+
+ /**
+ * **Deprecated** - ALM HMI states converted back to HMI Levels
+ *
+ * HMI Full = onSdlInFocus(Boolean isFirstSdlInFocus);
+ * HMI Limited = onSdlInFocusLimited();
+ * HMI Background = onSdlLostFocus();
+ */
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerBase.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerBase.java
new file mode 100644
index 000000000..f5bfeaf57
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IProxyListenerBase.java
@@ -0,0 +1,359 @@
+package com.smartdevicelink.proxy.interfaces;
+
+import com.smartdevicelink.proxy.callbacks.OnServiceEnded;
+import com.smartdevicelink.proxy.callbacks.OnServiceNACKed;
+import com.smartdevicelink.proxy.rpc.AddCommandResponse;
+import com.smartdevicelink.proxy.rpc.AddSubMenuResponse;
+import com.smartdevicelink.proxy.rpc.AlertManeuverResponse;
+import com.smartdevicelink.proxy.rpc.AlertResponse;
+import com.smartdevicelink.proxy.rpc.ButtonPressResponse;
+import com.smartdevicelink.proxy.rpc.ChangeRegistrationResponse;
+import com.smartdevicelink.proxy.rpc.CreateInteractionChoiceSetResponse;
+import com.smartdevicelink.proxy.rpc.DeleteCommandResponse;
+import com.smartdevicelink.proxy.rpc.DeleteFileResponse;
+import com.smartdevicelink.proxy.rpc.DeleteInteractionChoiceSetResponse;
+import com.smartdevicelink.proxy.rpc.DeleteSubMenuResponse;
+import com.smartdevicelink.proxy.rpc.DiagnosticMessageResponse;
+import com.smartdevicelink.proxy.rpc.DialNumberResponse;
+import com.smartdevicelink.proxy.rpc.EndAudioPassThruResponse;
+import com.smartdevicelink.proxy.rpc.GenericResponse;
+import com.smartdevicelink.proxy.rpc.GetDTCsResponse;
+import com.smartdevicelink.proxy.rpc.GetInteriorVehicleDataResponse;
+import com.smartdevicelink.proxy.rpc.GetSystemCapabilityResponse;
+import com.smartdevicelink.proxy.rpc.GetVehicleDataResponse;
+import com.smartdevicelink.proxy.rpc.GetWayPointsResponse;
+import com.smartdevicelink.proxy.rpc.ListFilesResponse;
+import com.smartdevicelink.proxy.rpc.OnAudioPassThru;
+import com.smartdevicelink.proxy.rpc.OnButtonEvent;
+import com.smartdevicelink.proxy.rpc.OnButtonPress;
+import com.smartdevicelink.proxy.rpc.OnCommand;
+import com.smartdevicelink.proxy.rpc.OnDriverDistraction;
+import com.smartdevicelink.proxy.rpc.OnHMIStatus;
+import com.smartdevicelink.proxy.rpc.OnHashChange;
+import com.smartdevicelink.proxy.rpc.OnInteriorVehicleData;
+import com.smartdevicelink.proxy.rpc.OnKeyboardInput;
+import com.smartdevicelink.proxy.rpc.OnLanguageChange;
+import com.smartdevicelink.proxy.rpc.OnLockScreenStatus;
+import com.smartdevicelink.proxy.rpc.OnPermissionsChange;
+import com.smartdevicelink.proxy.rpc.OnRCStatus;
+import com.smartdevicelink.proxy.rpc.OnStreamRPC;
+import com.smartdevicelink.proxy.rpc.OnSystemRequest;
+import com.smartdevicelink.proxy.rpc.OnTBTClientState;
+import com.smartdevicelink.proxy.rpc.OnTouchEvent;
+import com.smartdevicelink.proxy.rpc.OnVehicleData;
+import com.smartdevicelink.proxy.rpc.OnWayPointChange;
+import com.smartdevicelink.proxy.rpc.PerformAudioPassThruResponse;
+import com.smartdevicelink.proxy.rpc.PerformInteractionResponse;
+import com.smartdevicelink.proxy.rpc.PutFileResponse;
+import com.smartdevicelink.proxy.rpc.ReadDIDResponse;
+import com.smartdevicelink.proxy.rpc.ResetGlobalPropertiesResponse;
+import com.smartdevicelink.proxy.rpc.ScrollableMessageResponse;
+import com.smartdevicelink.proxy.rpc.SendHapticDataResponse;
+import com.smartdevicelink.proxy.rpc.SendLocationResponse;
+import com.smartdevicelink.proxy.rpc.SetAppIconResponse;
+import com.smartdevicelink.proxy.rpc.SetDisplayLayoutResponse;
+import com.smartdevicelink.proxy.rpc.SetGlobalPropertiesResponse;
+import com.smartdevicelink.proxy.rpc.SetInteriorVehicleDataResponse;
+import com.smartdevicelink.proxy.rpc.SetMediaClockTimerResponse;
+import com.smartdevicelink.proxy.rpc.ShowConstantTbtResponse;
+import com.smartdevicelink.proxy.rpc.ShowResponse;
+import com.smartdevicelink.proxy.rpc.SliderResponse;
+import com.smartdevicelink.proxy.rpc.SpeakResponse;
+import com.smartdevicelink.proxy.rpc.StreamRPCResponse;
+import com.smartdevicelink.proxy.rpc.SubscribeButtonResponse;
+import com.smartdevicelink.proxy.rpc.SubscribeVehicleDataResponse;
+import com.smartdevicelink.proxy.rpc.SubscribeWayPointsResponse;
+import com.smartdevicelink.proxy.rpc.SystemRequestResponse;
+import com.smartdevicelink.proxy.rpc.UnsubscribeButtonResponse;
+import com.smartdevicelink.proxy.rpc.UnsubscribeVehicleDataResponse;
+import com.smartdevicelink.proxy.rpc.UnsubscribeWayPointsResponse;
+import com.smartdevicelink.proxy.rpc.UpdateTurnListResponse;
+import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason;
+
+
+public interface IProxyListenerBase {
+
+ /**
+ * onOnHMIStatus being called indicates that there has been an HMI Level change,
+ * system context change or audio streaming state change.
+ *
+ * @param notification - Contains information about the HMI Level,
+ * system context and audio streaming state.
+ */
+ public void onOnHMIStatus(OnHMIStatus notification);
+
+ /**
+ * onProxyClosed has different functionality for the different models.
+ * In the non-ALM model this indicates that the proxy has experienced an unrecoverable error.
+ * A new proxy object must be initiated to reestablish connection with SDL.
+ * In the ALM model this indicates that the app is no longer registered with SDL
+ * All resources on SDL (addCommands and ChoiceSets) have been deleted and will have to be
+ * recreated upon the next onReadyForInitialization() call-back.
+ *
+ * @param info - Includes information about the reason the proxy has been closed.
+ * @param e - The exception that occurred.
+ */
+ public void onProxyClosed(String info, Exception e, SdlDisconnectedReason reason);
+
+ public void onServiceEnded(OnServiceEnded serviceEnded);
+
+ public void onServiceNACKed(OnServiceNACKed serviceNACKed);
+
+ public void onOnStreamRPC(OnStreamRPC notification);
+
+ public void onStreamRPCResponse(StreamRPCResponse response);
+
+ /**
+ * onProxyError() being called indicates that the SDL Proxy experenced an error.
+ *
+ * @param info - Includes information about the Exception that occurred.
+ * @param e - The exception that occurred.
+ */
+ public void onError(String info, Exception e);
+
+ /**
+ * onGenericResponse() being called indicates that SDL could not determine the
+ * type of request it is responding to. This is usually result of an unknown RPC Request
+ * being sent.
+ *
+ * @param response - Includes detailed information about the response.
+ */
+ public void onGenericResponse(GenericResponse response);
+
+ /**
+ * onOnCommand() being called indicates that the user selected a command on SDL.
+ *
+ * @param notification - Contains information about the command chosen.
+ */
+ public void onOnCommand(OnCommand notification);
+
+ /**
+ * onAddCommandResponse() being called indicates that SDL has responded to
+ * a request to add a command.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onAddCommandResponse(AddCommandResponse response);
+
+ /**
+ * onAddSubMenuResponse() being called indicates that SDL has responded to
+ * a request to add a command.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onAddSubMenuResponse(AddSubMenuResponse response);
+
+ /**
+ * onCreateInteractionChoiceSetResponse() being called indicates that SDL has
+ * responded to a request to add an interactionChoiceSet.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onCreateInteractionChoiceSetResponse(CreateInteractionChoiceSetResponse response);
+
+ /**
+ * onAlertResponse being called indicates that SDL has
+ * responded to a request to alert the user.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onAlertResponse(AlertResponse response);
+
+ /**
+ * onDeleteCommandResponse being called indicates that SDL has
+ * responded to a request to delete a command.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onDeleteCommandResponse(DeleteCommandResponse response);
+
+ /**
+ * onDeleteCommandResponse being called indicates that SDL has
+ * responded to a request to delete an interaction choice set.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onDeleteInteractionChoiceSetResponse(DeleteInteractionChoiceSetResponse response);
+
+ /**
+ * onDeleteCommandResponse being called indicates that SDL has
+ * responded to a request to delete a submenu.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onDeleteSubMenuResponse(DeleteSubMenuResponse response);
+
+ /**
+ * onPerformInteractionResponse being called indicates that SDL has
+ * responded to a request to perform an interaction.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onPerformInteractionResponse(PerformInteractionResponse response);
+
+ /**
+ * onResetGlobalPropertiesResponse being called indicates that SDL has
+ * responded to a request to reset global properties.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onResetGlobalPropertiesResponse(ResetGlobalPropertiesResponse response);
+
+ /**
+ * onSetGlobalPropertiesResponse being called indicates that SDL has
+ * responded to a request to set global properties.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onSetGlobalPropertiesResponse(SetGlobalPropertiesResponse response);
+
+ /**
+ * onSetMediaClockTimerResponse being called indicates that SDL has
+ * responded to a request to set the media clock timer.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onSetMediaClockTimerResponse(SetMediaClockTimerResponse response);
+
+ /**
+ * onShowResponse being called indicates that SDL has
+ * responded to a request to display information to the user.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onShowResponse(ShowResponse response);
+
+ /**
+ * onSpeakResponse being called indicates that SDL has
+ * responded to a request to speak information to the user.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onSpeakResponse(SpeakResponse response);
+
+ /**
+ * onButtonEvent being called indicates that a button event has occurred.
+ *
+ * @param notification - Contains information about the notification sent from SDL.
+ */
+ public void onOnButtonEvent(OnButtonEvent notification);
+
+ /**
+ * onButtonPress being called indicates that SDL has a button has
+ * been pressed by the user.
+ *
+ * @param notification - Contains information about the notification sent from SDL.
+ */
+ public void onOnButtonPress(OnButtonPress notification);
+
+ /**
+ * onSubscribeButtonResponse being called indicates that SDL has
+ * responded to a request to subscribe to button events and button presses.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onSubscribeButtonResponse(SubscribeButtonResponse response);
+
+ /**
+ * onUnsubscribeButtonResponse being called indicates that SDL has
+ * responded to a request to unsubscribe from button events and button presses.
+ *
+ * @param response - Contains information about the response sent from SDL.
+ */
+ public void onUnsubscribeButtonResponse(UnsubscribeButtonResponse response);
+
+ /**
+ * onOnPermissionsChange being called indicates that your app permissions have
+ * changed due to a policy table change. This can mean your app has received additional
+ * permissions OR lost permissions.
+ *
+ * @param notification - Contains information about the changed permissions.
+ */
+ public void onOnPermissionsChange(OnPermissionsChange notification);
+
+ public void onSubscribeVehicleDataResponse(SubscribeVehicleDataResponse response);
+
+ public void onUnsubscribeVehicleDataResponse(UnsubscribeVehicleDataResponse response);
+
+ public void onGetVehicleDataResponse(GetVehicleDataResponse response);
+
+ public void onOnVehicleData(OnVehicleData notification);
+
+ public void onPerformAudioPassThruResponse(PerformAudioPassThruResponse response);
+
+ public void onEndAudioPassThruResponse(EndAudioPassThruResponse response);
+
+ public void onOnAudioPassThru(OnAudioPassThru notification);
+
+ public void onPutFileResponse(PutFileResponse response);
+
+ public void onDeleteFileResponse(DeleteFileResponse response);
+
+ public void onListFilesResponse(ListFilesResponse response);
+
+ public void onSetAppIconResponse(SetAppIconResponse response);
+
+ public void onScrollableMessageResponse(ScrollableMessageResponse response);
+
+ public void onChangeRegistrationResponse(ChangeRegistrationResponse response);
+
+ public void onSetDisplayLayoutResponse(SetDisplayLayoutResponse response);
+
+ public void onOnLanguageChange(OnLanguageChange notification);
+
+ public void onOnHashChange(OnHashChange notification);
+
+ public void onSliderResponse(SliderResponse response);
+
+ public void onOnDriverDistraction(OnDriverDistraction notification);
+
+ public void onOnTBTClientState(OnTBTClientState notification);
+
+ public void onOnSystemRequest(OnSystemRequest notification);
+
+ public void onSystemRequestResponse(SystemRequestResponse response);
+
+ public void onOnKeyboardInput(OnKeyboardInput notification);
+
+ public void onOnTouchEvent(OnTouchEvent notification);
+
+ public void onDiagnosticMessageResponse(DiagnosticMessageResponse response);
+
+ public void onReadDIDResponse(ReadDIDResponse response);
+
+ public void onGetDTCsResponse(GetDTCsResponse response);
+
+ public void onOnLockScreenNotification(OnLockScreenStatus notification);
+
+ public void onDialNumberResponse(DialNumberResponse response);
+
+ public void onSendLocationResponse(SendLocationResponse response);
+
+ public void onShowConstantTbtResponse(ShowConstantTbtResponse response);
+
+ public void onAlertManeuverResponse(AlertManeuverResponse response);
+
+ public void onUpdateTurnListResponse(UpdateTurnListResponse response);
+
+ public void onServiceDataACK(int dataSize);
+ public void onGetWayPointsResponse(GetWayPointsResponse response);
+
+ public void onSubscribeWayPointsResponse(SubscribeWayPointsResponse response);
+
+ public void onUnsubscribeWayPointsResponse(UnsubscribeWayPointsResponse response);
+ public void onOnWayPointChange(OnWayPointChange notification);
+
+ public void onGetSystemCapabilityResponse(GetSystemCapabilityResponse response);
+
+ public void onGetInteriorVehicleDataResponse(GetInteriorVehicleDataResponse response);
+
+ public void onButtonPressResponse(ButtonPressResponse response);
+
+ public void onSetInteriorVehicleDataResponse(SetInteriorVehicleDataResponse response);
+
+ public void onOnInteriorVehicleData(OnInteriorVehicleData notification);
+
+ public void onSendHapticDataResponse(SendHapticDataResponse response);
+
+ public void onOnRCStatus(OnRCStatus notification);
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/IPutFileResponseListener.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IPutFileResponseListener.java
new file mode 100644
index 000000000..7929e86e6
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IPutFileResponseListener.java
@@ -0,0 +1,9 @@
+package com.smartdevicelink.proxy.interfaces;
+
+import com.smartdevicelink.proxy.rpc.PutFileResponse;
+
+public interface IPutFileResponseListener {
+ public void onPutFileResponse(PutFileResponse response);
+
+ public void onPutFileStreamError(Exception e, String info);
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java
new file mode 100644
index 000000000..5eb8ff36a
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdl.java
@@ -0,0 +1,229 @@
+package com.smartdevicelink.proxy.interfaces;
+
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.util.Version;
+import com.smartdevicelink.proxy.rpc.SdlMsgVersion;
+import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType;
+import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener;
+import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener;
+import com.smartdevicelink.streaming.audio.AudioStreamingCodec;
+import com.smartdevicelink.streaming.audio.AudioStreamingParams;
+import com.smartdevicelink.streaming.video.VideoStreamingParameters;
+
+import java.util.List;
+
+/*
+ * Copyright (c) 2017 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+@SuppressWarnings("unused")
+public interface ISdl {
+
+ /**
+ * Starts the connection with the module
+ */
+ void start();
+
+ /**
+ * Ends connection with the module
+ */
+ void stop();
+
+ /**
+ * Method to check if the session is connected
+ * @return if there is a connected session
+ */
+ boolean isConnected();
+
+ /**
+ * Add a service listener for a specific service type
+ * @param serviceType service type that the listener will be attached to
+ * @param sdlServiceListener listener for events that happen to the service
+ */
+ void addServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener);
+
+ /**
+ * Remote a service listener for a specific service type
+ * @param serviceType service type that the listener was attached to
+ * @param sdlServiceListener service listener that was previously added for the service type
+ */
+ void removeServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener);
+
+ /**
+ * Starts the video streaming service
+ * @param parameters desired video streaming params for this sevice to be started with
+ * @param encrypted flag to start this service with encryption or not
+ */
+ void startVideoService(VideoStreamingParameters parameters, boolean encrypted);
+
+ /**
+ * Stops the video service if open
+ */
+ void stopVideoService();
+
+ /**
+ * Starts the video streaming service
+ * @param isEncrypted flag to start this service with encryption or not
+ * @param parameters desired video streaming params for this sevice to be started with
+ */
+ IVideoStreamListener startVideoStream(boolean isEncrypted, VideoStreamingParameters parameters);
+
+ /**
+ * Starts the Audio streaming service
+ * @param encrypted flag to start this service with encryption or not
+ */
+ void startAudioService(boolean encrypted, AudioStreamingCodec codec, AudioStreamingParams params);
+
+ /**
+ * Starts the Audio streaming service
+ * @param encrypted flag to start this service with encryption or not
+ */
+ void startAudioService(boolean encrypted);
+
+ /**
+ * Stops the audio service if open
+ */
+ void stopAudioService();
+
+ /**
+ * Start Audio Stream and return IAudioStreamListener
+ * @param isEncrypted
+ * @param codec
+ * @param params
+ * @return IAudioStreamListener
+ */
+ IAudioStreamListener startAudioStream(boolean isEncrypted, AudioStreamingCodec codec, AudioStreamingParams params);
+
+ /**
+ * Pass an RPC message through the proxy to be sent to the connected module
+ * @param message RPCRequest that should be sent to the module
+ */
+ void sendRPCRequest(RPCRequest message);
+
+ /**
+ * Pass a list of RPC requests through the proxy to be sent to core
+ * @param rpcs List of RPC requests
+ * @param listener OnMultipleRequestListener that is called between requests and after all are processed
+ */
+ void sendRequests(List<? extends RPCRequest> rpcs, final OnMultipleRequestListener listener);
+
+ /**
+ * Add an OnRPCNotificationListener for specified notification
+ * @param notificationId FunctionID of the notification that is to be listened for
+ * @param listener listener that should be added for the notification ID
+ */
+ void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener);
+
+ /**
+ * Removes an OnRPCNotificationListener for specified notification
+ * @param notificationId FunctionID of the notification that was to be listened for
+ * @param listener listener that was previously added for the notification ID
+ */
+ boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener);
+
+ /**
+ * Add an OnRPCResponseListener for specified response
+ * @param responseId FunctionID of the response that is to be listened for
+ * @param listener listener that should be added for the response ID
+ */
+ void addOnRPCListener(FunctionID responseId, OnRPCListener listener);
+
+ /**
+ * Removes an OnRPCResponseListener for specified response
+ * @param responseId FunctionID of the response that was to be listened for
+ * @param listener listener that was previously added for the response ID
+ */
+ boolean removeOnRPCListener(FunctionID responseId, OnRPCListener listener);
+
+ /**
+ * Get SystemCapability Object
+ * @param systemCapabilityType
+ * @return Object
+ */
+ Object getCapability(SystemCapabilityType systemCapabilityType);
+
+ /**
+ * Get Capability
+ * @param systemCapabilityType
+ * @param scListener
+ */
+ void getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener);
+
+ /**
+ * Check if capability is supported
+ * @param systemCapabilityType
+ * @return Boolean
+ */
+ boolean isCapabilitySupported(SystemCapabilityType systemCapabilityType);
+
+ /**
+ * Add a listener to be called whenever a new capability is retrieved
+ * @param systemCapabilityType Type of capability desired
+ * @param listener callback to execute upon retrieving capability
+ */
+ void addOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener);
+
+ /**
+ * Remove an OnSystemCapabilityListener that was previously added
+ * @param systemCapabilityType Type of capability
+ * @param listener the listener that should be removed
+ */
+ boolean removeOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener);
+
+ /**
+ * Check to see if a transport is available to start/use the supplied service.
+ * @param serviceType the session that should be checked for transport availability
+ * @return true if there is either a supported
+ * transport currently connected or a transport is
+ * available to connect with for the supplied service type.
+ * <br>false if there is no
+ * transport connected to support the service type in question and
+ * no possibility in the foreseeable future.
+ */
+ boolean isTransportForServiceAvailable(SessionType serviceType);
+
+ /**
+ * Get the RPC specification version currently being used for the SDL messages
+ * @return SdlMsgVersion the current RPC specification version
+ */
+ @NonNull SdlMsgVersion getSdlMsgVersion();
+
+ /**
+ * Get the protocol version of this session
+ * @return byte value representing WiPro version
+ */
+ @NonNull Version getProtocolVersion();
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdlServiceListener.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdlServiceListener.java
new file mode 100644
index 000000000..db5179a73
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/ISdlServiceListener.java
@@ -0,0 +1,11 @@
+package com.smartdevicelink.proxy.interfaces;
+
+
+import com.smartdevicelink.SdlConnection.SdlSession;
+import com.smartdevicelink.protocol.enums.SessionType;
+
+public interface ISdlServiceListener {
+ public void onServiceStarted(SdlSession session, SessionType type, boolean isEncrypted);
+ public void onServiceEnded(SdlSession session, SessionType type);
+ public void onServiceError(SdlSession session, SessionType type, String reason);
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/IVideoStreamListener.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IVideoStreamListener.java
new file mode 100644
index 000000000..f645b0d55
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/IVideoStreamListener.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017, Xevo Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.proxy.interfaces;
+
+import java.nio.ByteBuffer;
+
+/**
+ * A listener that receives video streaming data from app.
+ */
+public interface IVideoStreamListener {
+
+ /**
+ * Sends a chunk of data which represents a frame to SDL Core.
+ * <p>
+ * The format of the chunk should align with MediaCodec's "Compressed Buffer" format, i.e. it
+ * should contain a single video frame, and it should start and end on frame boundaries.
+ * Please refer to https://developer.android.com/reference/android/media/MediaCodec.html
+ * Also, for H.264 codec case the stream must be in byte-stream format (also known as Annex-B
+ * format). This isn't explained in the document above, but MediaCodec does output in this
+ * format.
+ * <p>
+ * In short, you can just provide MediaCodec's data outputs to this method without tweaking
+ * any data.
+ * <p>
+ * Note: this method must not be called after SdlProxyBase.endVideoStream() is called.
+ *
+ * @param data Byte array containing a video frame
+ * @param offset Starting offset in 'data'
+ * @param length Length of the data
+ * @param presentationTimeUs Presentation timestamp (PTS) of this frame, in microseconds.
+ * It must be greater than the previous timestamp.
+ * Specify -1 if unknown.
+ * @throws ArrayIndexOutOfBoundsException When offset does not satisfy
+ * {@code 0 <= offset && offset <= data.length}
+ * or length does not satisfy
+ * {@code 0 < length && offset + length <= data.length}
+ */
+ void sendFrame(byte[] data, int offset, int length, long presentationTimeUs)
+ throws ArrayIndexOutOfBoundsException;
+
+ /**
+ * Sends chunks of data which represent a frame to SDL Core.
+ * <p>
+ * The format of the chunk should align with MediaCodec's "Compressed Buffer" format, i.e. it
+ * should contain a single video frame, and it should start and end on frame boundaries.
+ * Please refer to https://developer.android.com/reference/android/media/MediaCodec.html
+ * Also, for H.264 codec case the stream must be in byte-stream format (also known as Annex-B
+ * format). This isn't explained in the document above, but MediaCodec does output in this
+ * format.
+ * <p>
+ * In short, you can just provide MediaCodec's data outputs to this method without tweaking
+ * any data.
+ * <p>
+ * Note: this method must not be called after SdlProxyBase.endVideoStream() is called.
+ *
+ * @param data Data chunk to send. Its position will be updated upon return.
+ * @param presentationTimeUs Presentation timestamp (PTS) of this frame, in microseconds.
+ * It must be greater than the previous timestamp.
+ * Specify -1 if unknown.
+ */
+ void sendFrame(ByteBuffer data, long presentationTimeUs);
+}
diff --git a/base/src/main/java/com/smartdevicelink/proxy/interfaces/OnSystemCapabilityListener.java b/base/src/main/java/com/smartdevicelink/proxy/interfaces/OnSystemCapabilityListener.java
new file mode 100644
index 000000000..d6fb11b4d
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/proxy/interfaces/OnSystemCapabilityListener.java
@@ -0,0 +1,6 @@
+package com.smartdevicelink.proxy.interfaces;
+
+public interface OnSystemCapabilityListener {
+ void onCapabilityRetrieved(Object capability);
+ void onError(String info);
+}
diff --git a/base/src/main/java/com/smartdevicelink/security/BaseSdlSecurityBase.java b/base/src/main/java/com/smartdevicelink/security/BaseSdlSecurityBase.java
new file mode 100644
index 000000000..81830fabf
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/security/BaseSdlSecurityBase.java
@@ -0,0 +1,91 @@
+package com.smartdevicelink.security;
+
+import java.util.ArrayList;
+import java.util.List;
+import com.smartdevicelink.SdlConnection.SdlSession;
+import com.smartdevicelink.protocol.enums.SessionType;
+
+// FIXME find a better name :)
+abstract class BaseSdlSecurityBase {
+
+ protected SdlSession session = null;
+ protected String appId = null;
+ protected List<String> makeList = null;
+ protected boolean isInitSuccess = false;
+ protected byte sessionId = 0;
+ protected List<SessionType> startServiceList = new ArrayList<SessionType>();
+
+ public BaseSdlSecurityBase() {
+ }
+
+ public abstract void initialize();
+
+ public abstract Integer runHandshake(byte[] inputData,byte[] outputData);
+
+ public abstract Integer encryptData(byte[] inputData,byte[] outputData);
+
+ public abstract Integer decryptData(byte[] inputData,byte[] outputData);
+
+ public abstract void shutDown();
+
+ public void resetParams() {
+ session = null;
+ appId = null;
+ isInitSuccess = false;
+ startServiceList.clear();
+ }
+
+ public List<SessionType> getServiceList() {
+ return startServiceList;
+ }
+
+ public void handleInitResult(boolean val) {
+ if (session == null) return;
+
+ setInitSuccess(val);
+ session.onSecurityInitialized();
+ }
+
+ public void handleSdlSession(SdlSession val) {
+ if (val == null) return;
+
+ setSessionId(val.getSessionId());
+ setSdlSession(val);
+ }
+
+ private void setInitSuccess(boolean val) {
+ isInitSuccess = val;
+ }
+
+ public boolean getInitSuccess() {
+ return isInitSuccess;
+ }
+
+ private void setSessionId(byte val) {
+ sessionId = val;
+ }
+
+ public byte getSessionId() {
+ return sessionId;
+ }
+
+ private void setSdlSession(SdlSession val) {
+ session = val;
+ }
+
+ public String getAppId() {
+ return appId;
+ }
+
+ public void setAppId(String val) {
+ appId = val;
+ }
+
+ public List<String> getMakeList() {
+ return makeList;
+ }
+
+ public void setMakeList(List<String> val) {
+ makeList = val;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/security/ISecurityInitializedListener.java b/base/src/main/java/com/smartdevicelink/security/ISecurityInitializedListener.java
new file mode 100644
index 000000000..f18152ed0
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/security/ISecurityInitializedListener.java
@@ -0,0 +1,5 @@
+package com.smartdevicelink.security;
+
+public interface ISecurityInitializedListener {
+ public void onSecurityInitialized();
+}
diff --git a/base/src/main/java/com/smartdevicelink/streaming/AbstractPacketizer.java b/base/src/main/java/com/smartdevicelink/streaming/AbstractPacketizer.java
new file mode 100644
index 000000000..d50964bff
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/AbstractPacketizer.java
@@ -0,0 +1,82 @@
+package com.smartdevicelink.streaming;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+import com.smartdevicelink.SdlConnection.SdlSession;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.util.Version;
+
+abstract public class AbstractPacketizer {
+
+ protected IStreamListener _streamListener = null;
+ protected byte _rpcSessionID = 0;
+
+ protected SessionType _serviceType = null;
+ protected SdlSession _session = null;
+ protected InputStream is = null;
+ protected int bufferSize;
+ protected byte[] buffer;
+ protected boolean upts = false;
+ protected RPCRequest _request = null;
+ protected Version _wiproVersion = new Version("1.0.0");
+
+ //protected long ts = 0, intervalBetweenReports = 5000, delta = 0;
+ protected long intervalBetweenReports = 5000, delta = 0;
+
+ public AbstractPacketizer(IStreamListener streamListener, InputStream is, SessionType sType, byte rpcSessionID, SdlSession session) throws IOException, IllegalArgumentException {
+ this._streamListener = streamListener;
+ this.is = is;
+ _rpcSessionID = rpcSessionID;
+ _serviceType = sType;
+ this._session = session;
+ if (this._session != null) {
+ bufferSize = this._session.getMtu();
+ buffer = new byte[bufferSize];
+ }else{
+ throw new IllegalArgumentException("Session variable is null");
+ }
+ }
+
+ @Deprecated
+ public AbstractPacketizer(IStreamListener streamListener, InputStream is, RPCRequest request, SessionType sType, byte rpcSessionID, byte wiproVersion, SdlSession session) throws IOException, IllegalArgumentException {
+ this._streamListener = streamListener;
+ this.is = is;
+ _rpcSessionID = rpcSessionID;
+ _serviceType = sType;
+ _request = request;
+ _wiproVersion = new Version(wiproVersion+".0.0");
+ this._session = session;
+ if (this._session != null) {
+ bufferSize = this._session.getMtu();
+ buffer = new byte[bufferSize];
+ }else{
+ throw new IllegalArgumentException("Session variable is null");
+ }
+ }
+
+ public AbstractPacketizer(IStreamListener streamListener, InputStream is, RPCRequest request, SessionType sType, byte rpcSessionID, Version protocolVersion, SdlSession session) throws IOException, IllegalArgumentException {
+ this._streamListener = streamListener;
+ this.is = is;
+ _rpcSessionID = rpcSessionID;
+ _serviceType = sType;
+ _request = request;
+ _wiproVersion = protocolVersion;
+ this._session = session;
+ if (this._session != null) {
+ bufferSize = this._session.getMtu();
+ buffer = new byte[bufferSize];
+ }else{
+ throw new IllegalArgumentException("Session variable is null");
+ }
+ }
+
+ public abstract void start() throws IOException;
+
+ public abstract void stop();
+
+ public abstract void pause();
+
+ public abstract void resume();
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/streaming/IStreamListener.java b/base/src/main/java/com/smartdevicelink/streaming/IStreamListener.java
new file mode 100644
index 000000000..5ec12bbaf
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/IStreamListener.java
@@ -0,0 +1,7 @@
+package com.smartdevicelink.streaming;
+
+import com.smartdevicelink.protocol.ProtocolMessage;
+
+public interface IStreamListener {
+ void sendStreamPacket(ProtocolMessage pm);
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java b/base/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java
new file mode 100644
index 000000000..ee57f2cd0
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/StreamPacketizer.java
@@ -0,0 +1,246 @@
+package com.smartdevicelink.streaming;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import com.smartdevicelink.SdlConnection.SdlSession;
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.interfaces.IAudioStreamListener;
+import com.smartdevicelink.proxy.interfaces.IVideoStreamListener;
+
+public class StreamPacketizer extends AbstractPacketizer implements IVideoStreamListener, IAudioStreamListener, Runnable{
+
+ public final static String TAG = "StreamPacketizer";
+
+ private Thread t = null;
+
+
+ private final static int TLS_MAX_RECORD_SIZE = 16384;
+ private final static int TLS_RECORD_HEADER_SIZE = 5;
+ private final static int TLS_RECORD_MES_AUTH_CDE_SIZE = 32;
+ private final static int TLS_MAX_RECORD_PADDING_SIZE = 256;
+
+
+ private final static int BUFF_READ_SIZE = TLS_MAX_RECORD_SIZE - TLS_RECORD_HEADER_SIZE - TLS_RECORD_MES_AUTH_CDE_SIZE - TLS_MAX_RECORD_PADDING_SIZE;
+
+ // Approximate size of data that mOutputQueue can hold in bytes.
+ // By adding a buffer, we accept underlying transport being stuck for a short time. By setting
+ // a limit of the buffer size, we avoid buffer overflows when underlying transport is too slow.
+ private static final int MAX_QUEUE_SIZE = 256 * 1024;
+
+ //FIXME public SdlConnection sdlConnection = null; //TODO remove completely
+ private Object mPauseLock;
+ private boolean mPaused;
+ private boolean isServiceProtected = false;
+ private BlockingQueue<ByteBuffer> mOutputQueue;
+
+ public StreamPacketizer(IStreamListener streamListener, InputStream is, SessionType sType, byte rpcSessionID, SdlSession session) throws IOException {
+ super(streamListener, is, sType, rpcSessionID, session);
+ mPauseLock = new Object();
+ mPaused = false;
+ isServiceProtected = _session.isServiceProtected(_serviceType);
+ if (bufferSize == 0) {
+ // fail safe
+ bufferSize = BUFF_READ_SIZE;
+ }
+ if(isServiceProtected){ //If our service is encrypted we can only use 1024 as the max buffer size.
+ bufferSize = BUFF_READ_SIZE;
+ buffer = new byte[bufferSize];
+ }
+ mOutputQueue = new LinkedBlockingQueue<ByteBuffer>(MAX_QUEUE_SIZE / bufferSize);
+ }
+
+ public void start() throws IOException {
+ if (t == null) {
+ t = new Thread(this);
+ t.start();
+ }
+ }
+
+ public void stop() {
+
+ if (t != null)
+ {
+ t.interrupt();
+ t = null;
+ }
+
+ }
+
+ public void run() {
+ int length;
+ try
+ {
+ while (t != null && !t.isInterrupted())
+ {
+ synchronized(mPauseLock)
+ {
+ while (mPaused)
+ {
+ try
+ {
+ mPauseLock.wait();
+ }
+ catch (InterruptedException e) {}
+ }
+ }
+
+ if (is != null) { // using InputStream interface
+ length = is.read(buffer, 0, bufferSize);
+
+ if (length >= 0) {
+ ProtocolMessage pm = new ProtocolMessage();
+ pm.setSessionID(_rpcSessionID);
+ pm.setSessionType(_serviceType);
+ pm.setFunctionID(0);
+ pm.setCorrID(0);
+ pm.setData(buffer, length);
+ pm.setPayloadProtected(isServiceProtected);
+
+ if (t != null && !t.isInterrupted()) {
+ _streamListener.sendStreamPacket(pm);
+ }
+ }
+ } else { // using sendFrame interface
+ ByteBuffer frame;
+ try {
+ frame = mOutputQueue.take();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+
+ while (frame.hasRemaining()) {
+ int len = frame.remaining() > bufferSize ? bufferSize : frame.remaining();
+
+ ProtocolMessage pm = new ProtocolMessage();
+ pm.setSessionID(_rpcSessionID);
+ pm.setSessionType(_serviceType);
+ pm.setFunctionID(0);
+ pm.setCorrID(0);
+ pm.setData(frame.array(), frame.arrayOffset() + frame.position(), len);
+ pm.setPayloadProtected(isServiceProtected);
+
+ if (t != null && !t.isInterrupted()) {
+ _streamListener.sendStreamPacket(pm);
+ }
+
+ frame.position(frame.position() + len);
+ }
+ }
+ }
+ } catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ if(_session == null) {
+ /* FIXME if (sdlConnection != null) {
+ sdlConnection.endService(_serviceType, _rpcSessionID);
+ }*/
+ }else{
+ _session.endService(_serviceType,_rpcSessionID);
+ }
+
+
+ }
+ }
+
+ @Override
+ public void pause() {
+ synchronized (mPauseLock) {
+ mPaused = true;
+ }
+ }
+
+ @Override
+ public void resume() {
+ synchronized (mPauseLock) {
+ mPaused = false;
+ mPauseLock.notifyAll();
+ }
+ }
+
+ /**
+ * Called by the app.
+ *
+ * @see IVideoStreamListener#sendFrame(byte[], int, int, long)
+ */
+ @Override
+ public void sendFrame(byte[] data, int offset, int length, long presentationTimeUs)
+ throws ArrayIndexOutOfBoundsException {
+ sendArrayData(data, offset, length);
+ }
+
+ /**
+ * Called by the app.
+ *
+ * @see IVideoStreamListener#sendFrame(ByteBuffer, long)
+ */
+ @Override
+ public void sendFrame(ByteBuffer data, long presentationTimeUs) {
+ sendByteBufferData(data);
+ }
+
+ /**
+ * Called by the app.
+ *
+ * @see IAudioStreamListener#sendAudio(byte[], int, int, long)
+ */
+ @Override
+ public void sendAudio(byte[] data, int offset, int length, long presentationTimeUs)
+ throws ArrayIndexOutOfBoundsException {
+ sendArrayData(data, offset, length);
+ }
+
+ /**
+ * Called by the app.
+ *
+ * @see IAudioStreamListener#sendAudio(ByteBuffer, long)
+ */
+ @Override
+ public void sendAudio(ByteBuffer data, long presentationTimeUs) {
+ sendByteBufferData(data);
+ }
+
+ private void sendArrayData(byte[] data, int offset, int length)
+ throws ArrayIndexOutOfBoundsException {
+ if (offset < 0 || offset > data.length || length <= 0 || offset + length > data.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+
+ // StreamPacketizer does not need to split a video frame into NAL units
+ ByteBuffer buffer = ByteBuffer.allocate(length);
+ buffer.put(data, offset, length);
+ buffer.flip();
+
+ try {
+ mOutputQueue.put(buffer);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
+ private void sendByteBufferData(ByteBuffer data) {
+ if (data == null || data.remaining() == 0) {
+ return;
+ }
+
+ // copy the whole buffer, so that even if the app modifies original ByteBuffer after
+ // sendFrame() or sendAudio() call, our buffer will stay intact
+ ByteBuffer buffer = ByteBuffer.allocate(data.remaining());
+ buffer.put(data);
+ buffer.flip();
+
+ try {
+ mOutputQueue.put(buffer);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/streaming/StreamRPCPacketizer.java b/base/src/main/java/com/smartdevicelink/streaming/StreamRPCPacketizer.java
new file mode 100644
index 000000000..1e3821ff8
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/StreamRPCPacketizer.java
@@ -0,0 +1,308 @@
+package com.smartdevicelink.streaming;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Hashtable;
+
+import com.smartdevicelink.SdlConnection.SdlSession;
+
+import com.smartdevicelink.marshal.JsonRPCMarshaller;
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.protocol.enums.MessageType;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.proxy.interfaces.IProxyListenerBase;
+import com.smartdevicelink.proxy.interfaces.IPutFileResponseListener;
+import com.smartdevicelink.proxy.rpc.OnStreamRPC;
+import com.smartdevicelink.proxy.rpc.PutFile;
+import com.smartdevicelink.proxy.rpc.PutFileResponse;
+import com.smartdevicelink.proxy.rpc.StreamRPCResponse;
+import com.smartdevicelink.proxy.rpc.enums.Result;
+import com.smartdevicelink.proxy.rpc.listeners.OnPutFileUpdateListener;
+import com.smartdevicelink.util.Version;
+
+public class StreamRPCPacketizer extends AbstractPacketizer implements IPutFileResponseListener, Runnable{
+
+ private Integer iInitialCorrID = 0;
+ private Hashtable<Integer, OnStreamRPC> notificationList = new Hashtable<Integer, OnStreamRPC>();
+ private Thread thread = null;
+ private long lFileSize = 0;
+ private String sFileName;
+ private Object proxy; //FIXME SdlProxyBase<IProxyListenerBase> _proxy;
+ private IProxyListenerBase _proxyListener;
+
+ private Object mPauseLock;
+ private boolean mPaused;
+ private boolean isRPCProtected = false;
+ private OnPutFileUpdateListener callBack;
+
+ private Version rpcSpecVersion;
+
+ //public StreamRPCPacketizer(SdlProxyBase<IProxyListenerBase> proxy, IStreamListener streamListener, InputStream is, RPCRequest request, SessionType sType, byte rpcSessionID, byte wiproVersion, long lLength, SdlSession session) throws IOException {
+ public StreamRPCPacketizer(Object proxy, IStreamListener streamListener, InputStream is, RPCRequest request, SessionType sType, byte rpcSessionID, byte wiproVersion, long lLength, SdlSession session) throws IOException {
+ super(streamListener, is, request, sType, rpcSessionID, wiproVersion, session);
+ lFileSize = lLength;
+ iInitialCorrID = request.getCorrelationID();
+ mPauseLock = new Object();
+ mPaused = false;
+ isRPCProtected = request.isPayloadProtected();
+ /* FIXME if (proxy != null)
+ {
+ _proxy = proxy;
+ _proxyListener = _proxy.getProxyListener();
+ _proxy.addPutFileResponseListener(this);
+ }*/
+ if(_request.getFunctionName().equalsIgnoreCase(FunctionID.PUT_FILE.toString())){
+ callBack = ((PutFile)_request).getOnPutFileUpdateListener();
+ }
+ }
+
+ //public StreamRPCPacketizer(SdlProxyBase<IProxyListenerBase> proxy, IStreamListener streamListener, InputStream is, RPCRequest request, SessionType sType, byte rpcSessionID, Version wiproVersion, Version rpcSpecVersion, long lLength, SdlSession session) throws IOException {
+ public StreamRPCPacketizer(Object proxy, IStreamListener streamListener, InputStream is, RPCRequest request, SessionType sType, byte rpcSessionID, Version wiproVersion, Version rpcSpecVersion, long lLength, SdlSession session) throws IOException {
+ super(streamListener, is, request, sType, rpcSessionID, wiproVersion, session);
+ this.rpcSpecVersion = rpcSpecVersion;
+ lFileSize = lLength;
+ iInitialCorrID = request.getCorrelationID();
+ mPauseLock = new Object();
+ mPaused = false;
+ isRPCProtected = request.isPayloadProtected();
+ /* FIXME if (proxy != null) {
+ _proxy = proxy;
+ _proxyListener = _proxy.getProxyListener();
+ _proxy.addPutFileResponseListener(this);
+ }*/
+ if(_request.getFunctionName().equalsIgnoreCase(FunctionID.PUT_FILE.toString())){
+ callBack = ((PutFile)_request).getOnPutFileUpdateListener();
+ }
+ }
+
+ @Override
+ public void start() throws IOException {
+ if (thread == null) {
+ thread = new Thread(this);
+ thread.start();
+ }
+ }
+
+ @Override
+ public void stop() {
+ try {
+ is.close();
+ } catch (IOException ignore) {}
+ if (thread != null)
+ {
+ thread.interrupt();
+ thread = null;
+ }
+ }
+
+ private void handleStreamSuccess(RPCResponse rpc, Long iSize)
+ {
+ StreamRPCResponse result = new StreamRPCResponse();
+ result.setSuccess(rpc.getSuccess());
+ result.setResultCode(rpc.getResultCode());
+ result.setInfo(rpc.getInfo());
+ result.setFileName(sFileName);
+ result.setFileSize(iSize);
+ result.setCorrelationID(iInitialCorrID);
+ if (_proxyListener != null)
+ _proxyListener.onStreamRPCResponse(result);
+ stop();
+ //FIXME _proxy.remPutFileResponseListener(this);
+ return;
+ }
+
+ private void handleStreamException(RPCResponse rpc, Exception e, String error)
+ {
+ StreamRPCResponse result = new StreamRPCResponse();
+ result.setFileName(sFileName);
+ result.setCorrelationID(iInitialCorrID);
+ if (rpc != null)
+ {
+ result.setSuccess(rpc.getSuccess());
+ result.setResultCode(rpc.getResultCode());
+ result.setInfo(rpc.getInfo());
+ }
+ else
+ {
+ result.setSuccess(false);
+ result.setResultCode(Result.GENERIC_ERROR);
+ String sException = "";
+
+ if (e != null)
+ sException = sException + " " + e.toString();
+
+ sException = sException + " " + error;
+ result.setInfo(sException);
+ }
+ if (_proxyListener != null)
+ _proxyListener.onStreamRPCResponse(result);
+ if (e != null)
+ e.printStackTrace();
+ stop();
+ //FIXME _proxy.remPutFileResponseListener(this);
+ return;
+ }
+
+ @Override
+ public void pause() {
+ synchronized (mPauseLock) {
+ mPaused = true;
+ }
+ }
+
+ @Override
+ public void resume() {
+ synchronized (mPauseLock) {
+ mPaused = false;
+ mPauseLock.notifyAll();
+ }
+ }
+
+ public void run() {
+ int length;
+ byte[] msgBytes;
+ ProtocolMessage pm;
+ OnStreamRPC notification;
+
+ // Moves the current Thread into the background
+ //FIXME android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
+
+ try {
+
+ int iCorrID = 0;
+ PutFile msg = (PutFile) _request;
+ sFileName = msg.getSdlFileName();
+ long iOffsetCounter = msg.getOffset();
+
+ int priorityCoefficient = 1;
+
+ if (lFileSize != 0)
+ {
+ Long iFileSize = (long) lFileSize;
+ //TODO: PutFile RPC needs to be updated to accept Long as we might run into overflows since a Long can store a wider range than an Integer
+ msg.setLength(iFileSize);
+ }
+ Long iFileLength = msg.getLength();
+
+ notificationList.clear();
+
+ //start reading from the stream at the given offset
+ long iSkipBytes = is.skip(iOffsetCounter);
+
+ if (iOffsetCounter != iSkipBytes)
+ {
+ handleStreamException(null,null," Error, PutFile offset invalid for file: " + sFileName);
+ }
+ if(callBack!=null){
+ callBack.onStart(_request.getCorrelationID(), lFileSize);
+ }
+ while (!Thread.interrupted()) {
+
+ synchronized (mPauseLock)
+ {
+ while (mPaused)
+ {
+ try
+ {
+ mPauseLock.wait();
+ }
+ catch (InterruptedException e) {}
+ }
+ }
+
+ length = is.read(buffer, 0, bufferSize);
+
+ if (length == -1)
+ stop();
+
+ if (length >= 0) {
+
+ if (msg.getOffset() != 0)
+ msg.setLength((Long)null); //only need to send length when offset 0
+
+ msg.format(rpcSpecVersion,true);
+ msgBytes = JsonRPCMarshaller.marshall(msg, (byte)_wiproVersion.getMajor());
+ pm = new ProtocolMessage();
+ pm.setData(msgBytes);
+
+ pm.setSessionID(_rpcSessionID);
+ pm.setMessageType(MessageType.RPC);
+ pm.setSessionType(_serviceType);
+ pm.setFunctionID(FunctionID.getFunctionId(msg.getFunctionName()));
+
+ if (buffer.length != length)
+ pm.setBulkData(buffer, length);
+ else
+ pm.setBulkDataNoCopy(buffer);
+
+ pm.setCorrID(msg.getCorrelationID());
+ pm.setPayloadProtected(isRPCProtected);
+ priorityCoefficient++;
+ pm.setPriorityCoefficient(priorityCoefficient);
+
+ notification = new OnStreamRPC();
+ notification.setFileName(msg.getSdlFileName());
+ notification.setFileSize(iFileLength);
+ iOffsetCounter = iOffsetCounter + length;
+ notification.setBytesComplete(iOffsetCounter);
+ notificationList.put(msg.getCorrelationID(),notification);
+
+ msg.setOffset(iOffsetCounter);
+ iCorrID = msg.getCorrelationID() + 1;
+ msg.setCorrelationID(iCorrID);
+
+ _streamListener.sendStreamPacket(pm);
+ }
+ }
+ } catch (Exception e) {
+ handleStreamException(null, e, "");
+ }
+ }
+
+ @Override
+ public void onPutFileResponse(PutFileResponse response)
+ {
+
+ OnStreamRPC streamNote = notificationList.get(response.getCorrelationID());
+ if (streamNote == null) return;
+
+ if (response.getSuccess())
+ {
+ if(callBack!=null){
+ callBack.onUpdate(response.getCorrelationID(), streamNote.getBytesComplete(), lFileSize);
+ }
+ if (_proxyListener != null){
+ _proxyListener.onOnStreamRPC(streamNote);
+ }
+
+ }
+ else
+ {
+ if(callBack!=null){
+ callBack.onError(response.getCorrelationID(), response.getResultCode(), response.getInfo());
+ }
+ handleStreamException(response, null, "");
+
+ }
+
+ if (response.getSuccess() && streamNote.getBytesComplete().equals(streamNote.getFileSize()) )
+ {
+ if(callBack!=null){
+ callBack.onResponse(iInitialCorrID, response, streamNote.getBytesComplete());
+ }
+ handleStreamSuccess(response, streamNote.getBytesComplete());
+
+ }
+ }
+
+ @Override
+ public void onPutFileStreamError(Exception e, String info)
+ {
+ if (thread != null)
+ handleStreamException(null, e, info);
+
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingCodec.java b/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingCodec.java
new file mode 100644
index 000000000..bf69a792b
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingCodec.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2017, Xevo Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.streaming.audio;
+
+/**
+ * Enum for each type of audio streaming codec.
+ */
+public enum AudioStreamingCodec {
+ /**
+ * Linear-PCM without any compression.
+ */
+ LPCM;
+
+ public static AudioStreamingCodec valueForString(String value) {
+ try {
+ return valueOf(value);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingLPCMParams.java b/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingLPCMParams.java
new file mode 100644
index 000000000..bde6abc4c
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingLPCMParams.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (c) 2017, Xevo Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.streaming.audio;
+
+/**
+ * A struct to hold LPCM specific audio format information.
+ */
+public class AudioStreamingLPCMParams extends AudioStreamingParams {
+ /**
+ * Sample format of linear PCM data.
+ */
+ public enum SampleFormat {
+ /**
+ * LPCM data is represented by 8-bit unsigned integers. Centerpoint is 128.
+ */
+ LPCM_8BIT_UNSIGNED,
+
+ /**
+ * LPCM data is represented by 16-bit signed integers, in little endian.
+ */
+ LPCM_16BIT_SIGNED_LITTLE_ENDIAN,
+ }
+
+ /**
+ * Sample format in which app will provide LPCM data to
+ * IAudioStreamListener.sendAudio()
+ * <p>
+ * This is reserved for future and not used right now.
+ */
+ public SampleFormat sampleFormat;
+
+ public AudioStreamingLPCMParams(SampleFormat sampleFormat, int samplingRate, int channels) {
+ super(samplingRate, channels);
+ this.sampleFormat = sampleFormat;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingParams.java b/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingParams.java
new file mode 100644
index 000000000..fe0f7a981
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/audio/AudioStreamingParams.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2017, Xevo Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.streaming.audio;
+
+/**
+ * A struct to hold audio format information that are common to codecs.
+ */
+public class AudioStreamingParams {
+ /**
+ * Sampling rate in Hz, e.g. 44100
+ * <p>
+ * This is reserved for future and not used right now.
+ */
+ public int samplingRate;
+
+ /**
+ * Number of channels in the audio stream
+ * <p>
+ * This is reserved for future and not used right now.
+ */
+ public int channels;
+
+ public AudioStreamingParams(int samplingRate, int channels) {
+ this.samplingRate = samplingRate;
+ this.channels = channels;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java b/base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java
new file mode 100644
index 000000000..73e8a65d3
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/video/RTPH264Packetizer.java
@@ -0,0 +1,503 @@
+/*
+ * Copyright (c) 2017, Xevo Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.streaming.video;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Random;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import com.smartdevicelink.SdlConnection.SdlSession;
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.interfaces.IVideoStreamListener;
+import com.smartdevicelink.streaming.AbstractPacketizer;
+import com.smartdevicelink.streaming.IStreamListener;
+
+/*
+ * Note for testing.
+ * The RTP stream generated by this packetizer can be tested with GStreamer (1.4 or later).
+ * Assuming that "VideoStreamPort" is configured as 5050 in smartDeviceLink.ini, here is the
+ * GStreamer pipeline that receives the stream, decode it and render it:
+ *
+ * $ gst-launch-1.0 souphttpsrc location=http://127.0.0.1:5050 ! "application/x-rtp-stream" ! rtpstreamdepay ! "application/x-rtp,media=(string)video,clock-rate=90000,encoding-name=(string)H264" ! rtph264depay ! "video/x-h264, stream-format=(string)avc, alignment=(string)au" ! avdec_h264 ! autovideosink sync=false
+ */
+
+/**
+ * This class receives H.264 byte stream (in Annex-B format), parses it, construct RTP packets
+ * from it based on RFC 6184, then frame the packets based on RFC 4571.
+ * The primary purpose of using RTP is to carry timestamp information along with the data.
+ *
+ * @author Sho Amano
+ */
+public class RTPH264Packetizer extends AbstractPacketizer implements IVideoStreamListener, Runnable {
+
+ // Approximate size of data that mOutputQueue can hold in bytes.
+ // By adding a buffer, we accept underlying transport being stuck for a short time. By setting
+ // a limit of the buffer size, we avoid buffer overflows when underlying transport is too slow.
+ private static final int MAX_QUEUE_SIZE = 256 * 1024;
+
+ private static final int FRAME_LENGTH_LEN = 2;
+ private static final int MAX_RTP_PACKET_SIZE = 65535; // because length field is two bytes (RFC 4571)
+ private static final int RTP_HEADER_LEN = 12;
+ private static final byte DEFAULT_RTP_PAYLOAD_TYPE = 96;
+ private static final int FU_INDICATOR_LEN = 1;
+ private static final int FU_HEADER_LEN = 1;
+ private static final byte TYPE_FU_A = 28;
+
+ // To align with StreamPacketizer class
+ private final static int TLS_MAX_RECORD_SIZE = 16384;
+ private final static int TLS_RECORD_HEADER_SIZE = 5;
+ private final static int TLS_RECORD_MES_AUTH_CDE_SIZE = 32;
+ private final static int TLS_MAX_RECORD_PADDING_SIZE = 256;
+
+ private final static int MAX_DATA_SIZE_FOR_ENCRYPTED_SERVICE =
+ TLS_MAX_RECORD_SIZE - TLS_RECORD_HEADER_SIZE - TLS_RECORD_MES_AUTH_CDE_SIZE- TLS_MAX_RECORD_PADDING_SIZE;
+
+ private boolean mServiceProtected;
+ private Thread mThread;
+ private BlockingQueue<ByteBuffer> mOutputQueue;
+ private volatile boolean mPaused;
+ private boolean mWaitForIDR;
+ private NALUnitReader mNALUnitReader;
+ private byte mPayloadType = 0;
+ private int mSSRC = 0;
+ private char mSequenceNum = 0;
+ private int mInitialPTS = 0;
+
+ /**
+ * Constructor
+ *
+ * @param streamListener The listener which this packetizer outputs SDL frames to
+ * @param serviceType The value of "Service Type" field in SDL frames
+ * @param sessionID The value of "Session ID" field in SDL frames
+ * @param session The SdlSession instance that this packetizer belongs to
+ */
+ public RTPH264Packetizer(IStreamListener streamListener,
+ SessionType serviceType, byte sessionID, SdlSession session) throws IOException {
+
+ super(streamListener, null, serviceType, sessionID, session);
+
+ mServiceProtected = session.isServiceProtected(_serviceType);
+
+ bufferSize = (int)this._session.getMtu(SessionType.NAV);
+ if (bufferSize == 0) {
+ // fail safe
+ bufferSize = MAX_DATA_SIZE_FOR_ENCRYPTED_SERVICE;
+ }
+ if (mServiceProtected && bufferSize > MAX_DATA_SIZE_FOR_ENCRYPTED_SERVICE) {
+ bufferSize = MAX_DATA_SIZE_FOR_ENCRYPTED_SERVICE;
+ }
+
+ mOutputQueue = new LinkedBlockingQueue<ByteBuffer>(MAX_QUEUE_SIZE / bufferSize);
+ mNALUnitReader = new NALUnitReader();
+ mPayloadType = DEFAULT_RTP_PAYLOAD_TYPE;
+
+ Random r = new Random();
+ mSSRC = r.nextInt();
+
+ // initial value of the sequence number and timestamp should be random ([5.1] in RFC3550)
+ mSequenceNum = (char)r.nextInt(65536);
+ mInitialPTS = r.nextInt();
+ }
+
+ /**
+ * Sets the Payload Type (PT) of RTP header field.
+ *
+ * Use this method if PT needs to be specified. The value should be between 0 and 127.
+ * Otherwise, a default value (96) is used.
+ *
+ * @param type A value indicating the Payload Type
+ */
+ public void setPayloadType(byte type) {
+ if (type >= 0 && type <= 127) {
+ mPayloadType = type;
+ } else {
+ mPayloadType = DEFAULT_RTP_PAYLOAD_TYPE;
+ }
+ }
+
+ /**
+ * Sets the SSRC of RTP header field.
+ *
+ * Use this method if SSRC needs to be specified. Otherwise, a random value is generated and
+ * used.
+ *
+ * @param ssrc An integer value representing SSRC
+ */
+ public void setSSRC(int ssrc) {
+ mSSRC = ssrc;
+ }
+
+ /**
+ * Starts this packetizer.
+ *
+ * It is recommended that the video encoder is started after the packetizer is started.
+ */
+ @Override
+ public void start() throws IOException {
+ if (mThread != null) {
+ return;
+ }
+
+ mThread = new Thread(this);
+ mThread.start();
+ }
+
+ /**
+ * Stops this packetizer.
+ *
+ * It is recommended that the video encoder is stopped prior to the packetizer.
+ */
+ @Override
+ public void stop() {
+ if (mThread == null) {
+ return;
+ }
+
+ mThread.interrupt();
+ mThread = null;
+
+ mPaused = false;
+ mWaitForIDR = false;
+ mOutputQueue.clear();
+ }
+
+ /**
+ * Pauses this packetizer.
+ *
+ * This pauses the packetizer but does not pause the video encoder.
+ */
+ @Override
+ public void pause() {
+ mPaused = true;
+ }
+
+ /**
+ * Resumes this packetizer.
+ */
+ @Override
+ public void resume() {
+ mWaitForIDR = true;
+ mPaused = false;
+ }
+
+ /**
+ * The thread routine.
+ */
+ public void run() {
+
+ while (mThread != null && !mThread.isInterrupted()) {
+ ByteBuffer frame;
+ try {
+ frame = mOutputQueue.take();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
+
+ while (frame.hasRemaining()) {
+ int len = frame.remaining() > bufferSize ? bufferSize : frame.remaining();
+
+ ProtocolMessage pm = new ProtocolMessage();
+ pm.setSessionID(_rpcSessionID);
+ pm.setSessionType(_serviceType);
+ pm.setFunctionID(0);
+ pm.setCorrID(0);
+ pm.setData(frame.array(), frame.arrayOffset() + frame.position(), len);
+ pm.setPayloadProtected(mServiceProtected);
+
+ _streamListener.sendStreamPacket(pm);
+
+ frame.position(frame.position() + len);
+ }
+ }
+
+ // XXX: This is added to sync with StreamPacketizer. Actually it shouldn't be here since
+ // it's confusing that a packetizer takes care of End Service request.
+ if (_session != null) {
+ _session.endService(_serviceType, _rpcSessionID);
+ }
+ }
+
+ /**
+ * Called by the app and encoder.
+ *
+ * @see IVideoStreamListener#sendFrame(byte[], int, int, long)
+ */
+ @Override
+ public void sendFrame(byte[] data, int offset, int length, long presentationTimeUs)
+ throws ArrayIndexOutOfBoundsException {
+ mNALUnitReader.init(data, offset, length);
+ onEncoderOutput(mNALUnitReader, presentationTimeUs);
+ }
+
+ /**
+ * Called by the app and encoder.
+ *
+ * @see IVideoStreamListener#sendFrame(ByteBuffer, long)
+ */
+ @Override
+ public void sendFrame(ByteBuffer data, long presentationTimeUs) {
+ mNALUnitReader.init(data);
+ onEncoderOutput(mNALUnitReader, presentationTimeUs);
+ }
+
+ private void onEncoderOutput(NALUnitReader nalUnitReader, long ptsInUs) {
+ if (mPaused) {
+ return;
+ }
+
+ ByteBuffer nalUnit;
+
+ while ((nalUnit = nalUnitReader.getNalUnit()) != null) {
+ if (mWaitForIDR) {
+ if (isIDR(nalUnit)) {
+ mWaitForIDR = false;
+ } else {
+ continue;
+ }
+ }
+ outputRTPFrames(nalUnit, ptsInUs, nalUnitReader.hasConsumedAll());
+ }
+ }
+
+ private boolean outputRTPFrames(ByteBuffer nalUnit, long ptsInUs, boolean isLast) {
+ if (RTP_HEADER_LEN + nalUnit.remaining() > MAX_RTP_PACKET_SIZE) {
+ // Split into multiple Fragmentation Units ([5.8] in RFC 6184)
+ byte firstByte = nalUnit.get();
+ boolean firstFragment = true;
+ boolean lastFragment = false;
+
+ while (nalUnit.remaining() > 0) {
+ int payloadLength = MAX_RTP_PACKET_SIZE - (RTP_HEADER_LEN + FU_INDICATOR_LEN + FU_HEADER_LEN);
+ if (nalUnit.remaining() <= payloadLength) {
+ payloadLength = nalUnit.remaining();
+ lastFragment = true;
+ }
+
+ ByteBuffer frame = allocateRTPFrame(FU_INDICATOR_LEN + FU_HEADER_LEN + payloadLength,
+ false, isLast, ptsInUs);
+ // FU indicator
+ frame.put((byte)((firstByte & 0xE0) | TYPE_FU_A));
+ // FU header
+ frame.put((byte)((firstFragment ? 0x80 : lastFragment ? 0x40 : 0) | (firstByte & 0x1F)));
+ // FU payload
+ frame.put(nalUnit.array(), nalUnit.position(), payloadLength);
+ nalUnit.position(nalUnit.position() + payloadLength);
+ frame.flip();
+
+ try {
+ mOutputQueue.put(frame);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+
+ firstFragment = false;
+ }
+ } else {
+ // Use Single NAL Unit Packet ([5.6] in RFC 6184)
+ ByteBuffer frame = allocateRTPFrame(nalUnit.remaining(), false, isLast, ptsInUs);
+ frame.put(nalUnit);
+ frame.flip();
+
+ try {
+ mOutputQueue.put(frame);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private ByteBuffer allocateRTPFrame(int rtpPayloadLen,
+ boolean hasPadding, boolean isLast, long ptsInUs) {
+ if (rtpPayloadLen <= 0) {
+ throw new IllegalArgumentException("Invalid rtpPayloadLen value: " + rtpPayloadLen);
+ }
+ if (ptsInUs < 0) {
+ throw new IllegalArgumentException("Invalid ptsInUs value: " + ptsInUs);
+ }
+
+ int packetLength = RTP_HEADER_LEN + rtpPayloadLen;
+ if (packetLength > MAX_RTP_PACKET_SIZE) {
+ throw new IllegalArgumentException("Invalid rtpPayloadLen value: " + rtpPayloadLen);
+ }
+ int ptsIn90kHz = (int)(ptsInUs * 9 / 100) + mInitialPTS;
+
+ ByteBuffer frame = ByteBuffer.allocate(FRAME_LENGTH_LEN + packetLength);
+ frame.order(ByteOrder.BIG_ENDIAN);
+ frame.putShort((short)packetLength);
+
+ // Version = 2, Padding = hasPadding, Extension = 0, CSRC count = 0
+ frame.put((byte)(0x80 | (hasPadding ? 0x20 : 0)))
+ // Marker = isLast, Payload type = mPayloadType
+ .put((byte)((isLast ? 0x80 : 0) | (mPayloadType & 0x7F)))
+ .putChar(mSequenceNum)
+ .putInt(ptsIn90kHz)
+ .putInt(mSSRC);
+
+ if (frame.position() != FRAME_LENGTH_LEN + RTP_HEADER_LEN) {
+ throw new RuntimeException("Data size in ByteBuffer mismatch");
+ }
+
+ mSequenceNum++;
+ return frame;
+ }
+
+ private static boolean isIDR(ByteBuffer nalUnit) {
+ if (nalUnit == null || !nalUnit.hasRemaining()) {
+ throw new IllegalArgumentException("Invalid nalUnit arg");
+ }
+
+ byte nalUnitType = (byte)(nalUnit.get(nalUnit.position()) & 0x1F);
+ return nalUnitType == 5;
+ }
+
+
+ private static int SKIP_TABLE[] = new int[256];
+ static {
+ // Sunday's quick search algorithm is used to find the start code.
+ // Prepare the table (SKIP_TABLE[0] = 2, SKIP_TABLE[1] = 1 and other elements will be 4).
+ byte[] NAL_UNIT_START_CODE = {0, 0, 1};
+ int searchStringLen = NAL_UNIT_START_CODE.length;
+ for (int i = 0; i < SKIP_TABLE.length; i++) {
+ SKIP_TABLE[i] = searchStringLen + 1;
+ }
+ for (int i = 0; i < searchStringLen; i++) {
+ SKIP_TABLE[NAL_UNIT_START_CODE[i] & 0xFF] = searchStringLen - i;
+ }
+ }
+
+ private class NALUnitReader {
+ private byte[] mData;
+ private int mOffset;
+ private int mLimit;
+
+ NALUnitReader() {
+ }
+
+ void init(byte[] data) {
+ mData = data;
+ mOffset = 0;
+ mLimit = data.length;
+ }
+
+ void init(byte[] data, int offset, int length) throws ArrayIndexOutOfBoundsException {
+ if (offset < 0 || offset > data.length || length <= 0 || offset + length > data.length) {
+ throw new ArrayIndexOutOfBoundsException();
+ }
+ mData = data;
+ mOffset = offset;
+ mLimit = offset + length;
+ }
+
+ void init(ByteBuffer data) {
+ if (data == null || data.remaining() == 0) {
+ mData = null;
+ mOffset = 0;
+ mLimit = 0;
+ return;
+ }
+
+ if (data.hasArray()) {
+ mData = data.array();
+ mOffset = data.position() + data.arrayOffset();
+ mLimit = mOffset + data.remaining();
+
+ // mark the buffer as consumed
+ data.position(data.position() + data.remaining());
+ } else {
+ byte[] buffer = new byte[data.remaining()];
+ data.get(buffer);
+
+ mData = buffer;
+ mOffset = 0;
+ mLimit = buffer.length;
+ }
+ }
+
+ ByteBuffer getNalUnit() {
+ if (hasConsumedAll()) {
+ return null;
+ }
+
+ int pos = mOffset;
+ int start = -1;
+
+ while (mLimit - pos >= 3) {
+ if (mData[pos] == 0 && mData[pos+1] == 0 && mData[pos+2] == 1) {
+ if (start != -1) {
+ // We've found a start code, a NAL unit and then another start code.
+ mOffset = pos;
+ // remove 0x00s in front of the start code
+ while (pos > start && mData[pos-1] == 0) {
+ pos--;
+ }
+ if (pos > start) {
+ return ByteBuffer.wrap(mData, start, pos - start);
+ } else {
+ // No NAL unit between two start codes?! Forget it and search for
+ // another start code.
+ pos = mOffset;
+ }
+ }
+ // This is the first start code.
+ pos += 3;
+ start = pos;
+ } else {
+ try {
+ pos += SKIP_TABLE[mData[pos+3] & 0xFF];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ break;
+ }
+ }
+ }
+
+ mOffset = mLimit;
+ if (start != -1 && mLimit > start) {
+ // We've found a start code and then reached to the end of array.
+ return ByteBuffer.wrap(mData, start, mLimit - start);
+ }
+ // A start code was not found
+ return null;
+ }
+
+ boolean hasConsumedAll() {
+ return (mData == null) || (mLimit - mOffset < 4);
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java b/base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java
new file mode 100644
index 000000000..0d16f7281
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/streaming/video/VideoStreamingParameters.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (c) 2017 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.streaming.video;
+
+//import android.util.DisplayMetrics;
+
+import com.smartdevicelink.proxy.rpc.ImageResolution;
+import com.smartdevicelink.proxy.rpc.VideoStreamingCapability;
+import com.smartdevicelink.proxy.rpc.VideoStreamingFormat;
+import com.smartdevicelink.proxy.rpc.enums.VideoStreamingCodec;
+import com.smartdevicelink.proxy.rpc.enums.VideoStreamingProtocol;
+
+import java.util.List;
+
+@SuppressWarnings("FieldCanBeLocal")
+public class VideoStreamingParameters {
+ private final VideoStreamingProtocol DEFAULT_PROTOCOL = VideoStreamingProtocol.RAW;
+ private final VideoStreamingCodec DEFAULT_CODEC = VideoStreamingCodec.H264;
+ private final int DEFAULT_WIDTH = 1024;
+ private final int DEFAULT_HEIGHT = 576;
+ private final int DEFAULT_DENSITY = 100; //FIXME DisplayMetrics.DENSITY_HIGH;
+ private final int DEFAULT_FRAMERATE = 30;
+ private final int DEFAULT_BITRATE = 512000;
+ private final int DEFAULT_INTERVAL = 5;
+
+
+ private int displayDensity;
+ private int frameRate;
+ private int bitrate;
+ private int interval;
+ private ImageResolution resolution;
+ private VideoStreamingFormat format;
+
+ public VideoStreamingParameters(){
+ displayDensity = DEFAULT_DENSITY;
+ frameRate = DEFAULT_FRAMERATE;
+ bitrate = DEFAULT_BITRATE;
+ interval = DEFAULT_INTERVAL;
+ resolution = new ImageResolution();
+ resolution.setResolutionWidth(DEFAULT_WIDTH);
+ resolution.setResolutionHeight(DEFAULT_HEIGHT);
+ format = new VideoStreamingFormat();
+ format.setProtocol(DEFAULT_PROTOCOL);
+ format.setCodec(DEFAULT_CODEC);
+ }
+
+ public VideoStreamingParameters(int displayDensity, int frameRate, int bitrate, int interval,
+ ImageResolution resolution, VideoStreamingFormat format){
+ this.displayDensity = displayDensity;
+ this.frameRate = frameRate;
+ this.bitrate = bitrate;
+ this.interval = interval;
+ this.resolution = resolution;
+ this.format = format;
+ }
+
+ /**
+ * Will only copy values that are not null or are greater than 0
+ * @param params VideoStreamingParameters that should be copied into this new instants
+ */
+ @SuppressWarnings("unused")
+ public VideoStreamingParameters(VideoStreamingParameters params){
+ update(params);
+ }
+
+ /**
+ * Will only copy values that are not null or are greater than 0
+ * @param params VideoStreamingParameters that should be copied into this new instants
+ */
+ public void update(VideoStreamingParameters params){
+ if(params!=null) {
+ if (params.displayDensity > 0) {
+ this.displayDensity = params.displayDensity;
+ }
+ if (params.frameRate > 0) {
+ this.frameRate = params.frameRate;
+ }
+ if (params.bitrate > 0) {
+ this.bitrate = params.bitrate;
+ }
+ if (params.interval > 0) {
+ this.interval = params.interval;
+ }
+ if (params.resolution != null) {
+ if (params.resolution.getResolutionHeight() != null && params.resolution.getResolutionHeight() > 0) {
+ this.resolution.setResolutionHeight(params.resolution.getResolutionHeight());
+ }
+ if (params.resolution.getResolutionWidth() != null && params.resolution.getResolutionWidth() > 0) {
+ this.resolution.setResolutionWidth(params.resolution.getResolutionWidth());
+ }
+ }
+ if (params.format != null) {
+ this.format = params.format;
+ }
+ }
+ }
+
+ /**
+ * Update the values contained in the capability that should have been returned through the SystemCapabilityManager.
+ * This update will use the most preferred streaming format from the module.
+ * @param capability the video streaming capability returned from the SystemCapabilityManager
+ * @see com.smartdevicelink.proxy.SystemCapabilityManager
+ * @see VideoStreamingCapability
+ */
+ public void update(VideoStreamingCapability capability){
+ if(capability.getMaxBitrate()!=null){ this.bitrate = capability.getMaxBitrate() * 1000; } // NOTE: the unit of maxBitrate in getSystemCapability is kbps.
+ ImageResolution resolution = capability.getPreferredResolution();
+ if(resolution!=null){
+ if(resolution.getResolutionHeight()!=null && resolution.getResolutionHeight() > 0){ this.resolution.setResolutionHeight(resolution.getResolutionHeight()); }
+ if(resolution.getResolutionWidth()!=null && resolution.getResolutionWidth() > 0){ this.resolution.setResolutionWidth(resolution.getResolutionWidth()); }
+ }
+ List<VideoStreamingFormat> formats = capability.getSupportedFormats();
+ if(formats != null && formats.size()>0){
+ this.format = formats.get(0);
+ }
+
+ }
+
+ @SuppressWarnings("unused")
+ public void setDisplayDensity(int displayDensity) {
+ this.displayDensity = displayDensity;
+ }
+
+ public int getDisplayDensity() {
+ return displayDensity;
+ }
+
+ public void setFrameRate(int frameRate) {
+ this.frameRate = frameRate;
+ }
+
+ public int getFrameRate() {
+ return frameRate;
+ }
+
+ public void setBitrate(int bitrate) {
+ this.bitrate = bitrate;
+ }
+
+ public int getBitrate() {
+ return bitrate;
+ }
+
+ public void setInterval(int interval) {
+ this.interval = interval;
+ }
+
+ public int getInterval() {
+ return interval;
+ }
+
+ public void setFormat(VideoStreamingFormat format){
+ this.format = format;
+ }
+
+ public VideoStreamingFormat getFormat(){
+ return format;
+ }
+
+ public void setResolution(ImageResolution resolution){
+ this.resolution = resolution;
+ }
+
+ public ImageResolution getResolution() {
+ return resolution;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("VideoStreamingParams - format: {");
+ builder.append(format.toString());
+ builder.append("}, resolution: {");
+ builder.append(resolution.getResolutionHeight());
+ builder.append(" , ");
+ builder.append(resolution.getResolutionWidth());
+ builder.append("}, frame rate {");
+ builder.append(frameRate);
+ builder.append("}, displayDensity{ ");
+ builder.append(displayDensity);
+ builder.append("}, bitrate");
+ builder.append(bitrate);
+ builder.append("}, IFrame interval{ ");
+ builder.append(interval);
+ builder.append("}");
+ return builder.toString();
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/trace/DiagLevel.java b/base/src/main/java/com/smartdevicelink/trace/DiagLevel.java
new file mode 100644
index 000000000..c88751378
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/DiagLevel.java
@@ -0,0 +1,61 @@
+package com.smartdevicelink.trace;
+
+import com.smartdevicelink.trace.enums.DetailLevel;
+import com.smartdevicelink.trace.enums.Mod;
+
+
+public class DiagLevel {
+
+ static private DetailLevel[] levels;
+
+ static { // this is a static c-tor!!
+ levels = new DetailLevel[Mod.values().length];
+ setAllLevels(DetailLevel.OFF);
+ }
+
+ public static void setAllLevels(DetailLevel thisDetail) {
+ if (thisDetail != null) {
+ for (int i = 0; i < levels.length; i++) {
+ levels[i] = thisDetail; //
+ }
+ }
+ }
+
+ public static void setLevel(Mod thisMod, DetailLevel thisDetail) {
+ if (thisMod != null && thisDetail != null) {
+ levels[thisMod.ordinal()] = thisDetail;
+ }
+ }
+
+ public static DetailLevel getLevel(Mod thisMod) {
+ if (thisMod != null) {
+ return levels[thisMod.ordinal()];
+ }
+ return null;
+ }
+
+ public static boolean isValidDetailLevel(String dtString) {
+ // Assume false
+ Boolean isValid = false;
+
+ if (dtString != null) {
+ if (dtString.equalsIgnoreCase("verbose"))
+ isValid = true;
+ else if (dtString.equalsIgnoreCase("terse"))
+ isValid = true;
+ else if (dtString.equalsIgnoreCase("off"))
+ isValid = true;
+ }
+
+ return isValid;
+ }
+
+ public static DetailLevel toDetailLevel(String dtString) {
+ DetailLevel dt = DetailLevel.OFF;
+ if (dtString.equalsIgnoreCase("verbose"))
+ dt = DetailLevel.VERBOSE;
+ else if (dtString.equalsIgnoreCase("terse"))
+ dt = DetailLevel.TERSE;
+ return dt;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/trace/ISTListener.java b/base/src/main/java/com/smartdevicelink/trace/ISTListener.java
new file mode 100644
index 000000000..35cc55de5
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/ISTListener.java
@@ -0,0 +1,5 @@
+package com.smartdevicelink.trace;
+
+public interface ISTListener {
+ void logXmlMsg(String msg, String token);
+} // end-interface \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/trace/Mime.java b/base/src/main/java/com/smartdevicelink/trace/Mime.java
new file mode 100644
index 000000000..173a017d6
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/Mime.java
@@ -0,0 +1,100 @@
+package com.smartdevicelink.trace;
+
+// Borrowed from Dave Boll's infamous SdlLinkRelay.java
+
+public class Mime {
+
+ private static final String BASE_64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+ /**
+ * @param str A String to encode into base64 String.
+ * @return Base64 encoded String or a null String if input is null.
+ */
+ public static String base64Encode(String str) {
+ if(str == null){
+ return null;
+ }
+
+ String b64String = "";
+ try {
+ byte[] strBytes = str.getBytes("US-ASCII");
+ b64String = base64Encode(strBytes);
+ } catch (Exception ex) {
+ // Don't care?
+ }
+ return b64String;
+ }
+
+ /**
+ * @param bytesToEncode A byte array to encode into base64 String.
+ * @return Base64 encoded String or a null String if input array is null.
+ */
+ public static String base64Encode(byte bytesToEncode[]) {
+ if(bytesToEncode != null){
+ return base64Encode(bytesToEncode, 0, bytesToEncode.length);
+ }
+ return null;
+ }
+
+ /**
+ * @param bytesToEncode A byte array to encode into base64 String.
+ * @param offset Offset to begin at
+ * @param length Length to read
+ * @return Base64 encoded String or a null String if input array is null or the input range is out of bounds.
+ */
+ public static String base64Encode(byte bytesToEncode[], int offset, int length) {
+ if (bytesToEncode == null || bytesToEncode.length < length || bytesToEncode.length < offset + length) {
+ return null;
+ }
+
+ StringBuilder sb = new StringBuilder();
+
+ int idxin = 0;
+ int b64idx = 0;
+
+ for (idxin = offset; idxin < offset + length; idxin++) {
+ switch ((idxin - offset) % 3) {
+ case 0:
+ b64idx = (bytesToEncode[idxin] >> 2) & 0x3f;
+ break;
+ case 1:
+ b64idx = (bytesToEncode[idxin] >> 4) & 0x0f;
+ b64idx |= ((bytesToEncode[idxin - 1] << 4) & 0x30);
+ break;
+ case 2:
+ b64idx = (bytesToEncode[idxin] >> 6) & 0x03;
+ b64idx |= ((bytesToEncode[idxin - 1] << 2) & 0x3c);
+ sb.append(getBase64Char(b64idx));
+ b64idx = bytesToEncode[idxin] & 0x3f;
+ break;
+ }
+ sb.append(getBase64Char(b64idx));
+ }
+
+ switch ((idxin - offset) % 3) {
+ case 0:
+ break;
+ case 1:
+ b64idx = (bytesToEncode[idxin - 1] << 4) & 0x30;
+ sb.append(getBase64Char(b64idx));
+ sb.append("==");
+ break;
+ case 2:
+ b64idx = ((bytesToEncode[idxin - 1] << 2) & 0x3c);
+ sb.append(getBase64Char(b64idx));
+ sb.append('=');
+ break;
+ }
+
+ return sb.toString();
+
+ }
+
+ private static char getBase64Char(int b64idx){
+ if(b64idx >= 0 && b64idx < BASE_64_CHARS.length()) {
+ return BASE_64_CHARS.charAt(b64idx);
+ }else{
+ return 0x20;
+ }
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/trace/OpenRPCMessage.java b/base/src/main/java/com/smartdevicelink/trace/OpenRPCMessage.java
new file mode 100644
index 000000000..13b690d91
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/OpenRPCMessage.java
@@ -0,0 +1,86 @@
+package com.smartdevicelink.trace;
+
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+
+import com.smartdevicelink.proxy.RPCMessage;
+import com.smartdevicelink.proxy.RPCStruct;
+
+class OpenRPCMessage extends RPCMessage {
+ private OpenRPCMessage() {super("");}
+ public OpenRPCMessage(RPCMessage rpcm) {
+ super(rpcm);
+ } // end-method
+
+ public OpenRPCMessage(RPCStruct rpcs) {
+ super(rpcs);
+ } // end-method
+
+ public String msgDump() {
+ StringBuilder pd = new StringBuilder();
+
+ pd.append(this.getFunctionName() + " " + this.getMessageType());
+
+ msgDump(pd);
+
+ return pd.toString();
+ } // end-method
+
+ public void msgDump(StringBuilder pd) {
+ pd.append("[");
+
+ dumpParams(parameters, pd);
+
+ pd.append("]");
+
+ return;
+ } // end-method
+
+ private void dumpParams(Hashtable<String, Object> ht, StringBuilder pd) {
+ Iterator<String> keySet = ht.keySet().iterator();
+ Object obj = null;
+ String key = "";
+ boolean isFirstParam = true;
+
+ while (keySet.hasNext()) {
+ key = (String)keySet.next();
+ obj = ht.get(key);
+ if (isFirstParam) {
+ isFirstParam = false;
+ } else {
+ pd.append(", ");
+ } // end-if
+
+ dumpParamNode(key, obj, pd);
+
+ } // end-while
+ } // end-method
+
+ @SuppressWarnings("unchecked")
+ private void dumpParamNode(String key, Object obj, StringBuilder pd) {
+
+ if (obj instanceof Hashtable) {
+ pd.append("[");
+ dumpParams((Hashtable<String, Object>)obj, pd);
+ pd.append("]");
+ } else if (obj instanceof RPCStruct) {
+ pd.append("[");
+ OpenRPCMessage orpcm = new OpenRPCMessage((RPCStruct)obj);
+ orpcm.msgDump(pd);
+ pd.append("]");
+ } else if (obj instanceof List) {
+ pd.append("[");
+ List<?> list = (List<?>)obj;
+ for (int idx=0;idx < list.size();idx++) {
+ if (idx > 0) {
+ pd.append(", ");
+ }
+ dumpParamNode(key, list.get(idx), pd);
+ } // end-for
+ pd.append("]");
+ } else {
+ pd.append("\"" + key + "\" = \"" + obj.toString() + "\"");
+ }
+ } // end-method
+} // end-class OpenRPCMessage
diff --git a/base/src/main/java/com/smartdevicelink/trace/SdlTrace.java b/base/src/main/java/com/smartdevicelink/trace/SdlTrace.java
new file mode 100644
index 000000000..fb50c0258
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/SdlTrace.java
@@ -0,0 +1,465 @@
+package com.smartdevicelink.trace;
+
+import java.sql.Timestamp;
+
+
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.protocol.enums.FrameDataControlFrameType;
+import com.smartdevicelink.protocol.enums.FrameType;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.RPCMessage;
+import com.smartdevicelink.proxy.RPCRequest;
+import com.smartdevicelink.proxy.RPCResponse;
+import com.smartdevicelink.trace.enums.DetailLevel;
+import com.smartdevicelink.trace.enums.InterfaceActivityDirection;
+import com.smartdevicelink.trace.enums.Mod;
+import com.smartdevicelink.transport.SiphonServer;
+import com.smartdevicelink.util.BitConverter;
+import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.NativeLogTool;
+
+/* This class handles the global TraceSettings as requested by the users either through the combination of the following
+ 1. System defaults
+ 2. Application XML config
+ 3. Programmatic requests from application itself
+
+ It is manifested in the <SmartDeviceLink>...</SmartDeviceLink> tags
+ */
+
+//@SuppressLint("DefaultLocale")
+public class SdlTrace {
+ private static final String SDL_LIB_TRACE_KEY = "42baba60-eb57-11df-98cf-0800200c9a66";
+
+ public static final String SYSTEM_LOG_TAG = "SdlTrace";
+
+ private static long baseTics = System.currentTimeMillis();
+ private static boolean acceptAPITraceAdjustments = true;
+
+ protected static ISTListener m_appTraceListener = null;
+
+ ///
+ /// The PUBLIC interface to SdlTrace starts here
+ ///
+
+
+ public static void setAcceptAPITraceAdjustments(Boolean APITraceAdjustmentsAccepted) {
+ if (APITraceAdjustmentsAccepted != null) {
+ acceptAPITraceAdjustments = APITraceAdjustmentsAccepted;
+ }
+ }
+
+ public static boolean getAcceptAPITraceAdjustments() {
+ return acceptAPITraceAdjustments;
+ }
+
+ public static void setAppTraceListener(ISTListener listener) {
+ m_appTraceListener = listener;
+ } // end-method
+
+ public static void setAppTraceLevel(DetailLevel dt) {
+ if ( dt != null && acceptAPITraceAdjustments)
+ DiagLevel.setLevel(Mod.app, dt);
+ } // end-method
+
+ public static void setProxyTraceLevel(DetailLevel dt) {
+ if (dt != null && acceptAPITraceAdjustments)
+ DiagLevel.setLevel(Mod.proxy, dt);
+ } // end-method
+
+ public static void setRpcTraceLevel(DetailLevel dt) {
+ if (dt != null && acceptAPITraceAdjustments)
+ DiagLevel.setLevel(Mod.rpc, dt);
+ } // end-method
+
+ public static void setMarshallingTraceLevel(DetailLevel dt) {
+ if (dt != null && acceptAPITraceAdjustments)
+ DiagLevel.setLevel(Mod.mar, dt);
+ } // end-method
+
+ public static void setProtocolTraceLevel(DetailLevel dt) {
+ if (dt != null && acceptAPITraceAdjustments)
+ DiagLevel.setLevel(Mod.proto, dt);
+ } // end-method
+
+ public static void setTransportTraceLevel(DetailLevel dt) {
+ if (dt != null && acceptAPITraceAdjustments)
+ DiagLevel.setLevel(Mod.tran, dt);
+ } // end-method
+
+ private static String encodeTraceMessage(long timestamp, Mod module, InterfaceActivityDirection msgDirection, String msgBodyXml) {
+ StringBuilder sb = new StringBuilder("<msg><dms>");
+ sb.append(timestamp);
+ sb.append("</dms><pid>");
+ //FIXME sb.append(Process.myPid());
+ sb.append("</pid><tid>");
+ sb.append(Thread.currentThread().getId());
+ sb.append("</tid><mod>");
+ sb.append(module.toString());
+ sb.append("</mod>");
+ if (msgDirection != InterfaceActivityDirection.None) {
+ sb.append("<dir>");
+ sb.append(interfaceActivityDirectionToString(msgDirection));
+ sb.append("</dir>");
+ } // end-if
+ sb.append(msgBodyXml);
+ sb.append("</msg>");
+
+ return sb.toString();
+ } // end-method
+
+ private static String interfaceActivityDirectionToString(InterfaceActivityDirection iaDirection) {
+ String str = "";
+ switch (iaDirection) {
+ case Receive:
+ str = "rx";
+ break;
+ case Transmit:
+ str = "tx";
+ break;
+ default:
+ break;
+ } // end-switch
+ return str;
+ } // end-method
+
+ static String B64EncodeForXML(String data) {
+ return Mime.base64Encode(data);
+ // Base64 only available in 2.2, when SmartDeviceLink base is 2.2 use: return Base64.encodeToString(data.getBytes(), Base64.DEFAULT);
+ } // end-method
+
+ public static boolean logProxyEvent(String eventText, String token) {
+ if (DiagLevel.getLevel(Mod.proxy) == DetailLevel.OFF || !token.equals(SDL_LIB_TRACE_KEY)) {
+ return false;
+ }
+
+ String msg = SdlTrace.B64EncodeForXML(eventText);
+ String xml = SdlTrace.encodeTraceMessage(SdlTrace.getBaseTicsDelta(), Mod.proxy, InterfaceActivityDirection.None, "<d>" + msg + "</d>");
+ return writeXmlTraceMessage(xml);
+ }
+
+ public static boolean logAppEvent(String eventText) {
+ if (DiagLevel.getLevel(Mod.app) == DetailLevel.OFF) {
+ return false;
+ }
+
+ long timestamp = SdlTrace.getBaseTicsDelta();
+ String msg = SdlTrace.B64EncodeForXML(eventText);
+ String xml = SdlTrace.encodeTraceMessage(timestamp, Mod.app, InterfaceActivityDirection.None, "<d>" + msg + "</d>");
+ return writeXmlTraceMessage(xml);
+ }
+
+ public static boolean logRPCEvent(InterfaceActivityDirection msgDirection, RPCMessage rpcMsg, String token) {
+ DetailLevel dl = DiagLevel.getLevel(Mod.rpc);
+ if (dl == DetailLevel.OFF || !token.equals(SDL_LIB_TRACE_KEY)) {
+ return false;
+ }
+
+ long timestamp = SdlTrace.getBaseTicsDelta();
+ String xml = SdlTrace.encodeTraceMessage(timestamp, Mod.rpc, msgDirection, rpc2Xml(dl, rpcMsg));
+ return writeXmlTraceMessage(xml);
+ }
+
+ private static String rpc2Xml(DetailLevel dl, RPCMessage rpcMsg) {
+ StringBuilder rpcAsXml = new StringBuilder();
+ rpcAsXml.append("<op>");
+ rpcAsXml.append(rpcMsg.getFunctionName());
+ rpcAsXml.append("</op>");
+ boolean hasCorrelationID = false;
+ Integer correlationID = -1;
+ if (rpcMsg instanceof RPCRequest) {
+ hasCorrelationID = true;
+ correlationID = ((RPCRequest)rpcMsg).getCorrelationID();
+ } else if (rpcMsg instanceof RPCResponse) {
+ hasCorrelationID = true;
+ correlationID = ((RPCResponse)rpcMsg).getCorrelationID();
+ } // end-if
+ if (hasCorrelationID) {
+ rpcAsXml.append("<cid>");
+ rpcAsXml.append(correlationID);
+ rpcAsXml.append("</cid>");
+ } // end-if
+ rpcAsXml.append("<type>");
+ rpcAsXml.append(rpcMsg.getMessageType());
+ rpcAsXml.append("</type>");
+ //rpcAsXml.append(newline);
+
+ if (dl == DetailLevel.VERBOSE) {
+ OpenRPCMessage orpcmsg = new OpenRPCMessage(rpcMsg);
+ String rpcParamList = orpcmsg.msgDump();
+ String msg = SdlTrace.B64EncodeForXML(rpcParamList);
+ rpcAsXml.append("<d>");
+ rpcAsXml.append(msg);
+ rpcAsXml.append("</d>");
+ } // end-if
+ return rpcAsXml.toString();
+ } // end-method
+
+ public static boolean logMarshallingEvent(InterfaceActivityDirection msgDirection, byte[] marshalledMessage, String token) {
+ DetailLevel dl = DiagLevel.getLevel(Mod.mar);
+ if (dl == DetailLevel.OFF || !token.equals(SDL_LIB_TRACE_KEY)) {
+ return false;
+ }
+
+ long timestamp = SdlTrace.getBaseTicsDelta();
+ StringBuilder msg = new StringBuilder();
+ msg.append("<sz>");
+ msg.append(marshalledMessage.length);
+ msg.append("</sz>");
+ if (dl == DetailLevel.VERBOSE) {
+ msg.append("<d>");
+ msg.append(Mime.base64Encode(marshalledMessage));
+ // Base64 only available in 2.2, when SmartDeviceLink base is 2.2 use: msg.append(Base64.encodeToString(marshalledMessage, Base64.DEFAULT));
+ msg.append("</d>");
+ }
+ String xml = SdlTrace.encodeTraceMessage(timestamp, Mod.mar, msgDirection, msg.toString());
+ return writeXmlTraceMessage(xml);
+ }
+
+ public static boolean logProtocolEvent(InterfaceActivityDirection frameDirection, SdlPacket packet, int frameDataOffset, int frameDataLength, String token) {
+ DetailLevel dl = DiagLevel.getLevel(Mod.proto);
+ if (dl == DetailLevel.OFF || !token.equals(SDL_LIB_TRACE_KEY)) {
+ return false;
+ }
+
+ StringBuffer protoMsg = new StringBuffer();
+ protoMsg.append("<frame>");
+ protoMsg.append(SdlTrace.getProtocolFrameHeaderInfo(packet));
+ if (dl == DetailLevel.VERBOSE) {
+ if (packet.getPayload() != null && frameDataLength > 0) {
+ protoMsg.append("<d>");
+ String bytesInfo = "";
+ bytesInfo = Mime.base64Encode(packet.getPayload(), frameDataOffset, frameDataLength);
+ // Base64 only available in 2.2, when SmartDeviceLink base is 2.2 use: bytesInfo = Base64.encodeToString(frameData, frameDataOffset, frameDataLength, Base64.DEFAULT);
+ protoMsg.append(bytesInfo);
+ protoMsg.append("</d>");
+ }
+ }
+ protoMsg.append("</frame>");
+ String xml = SdlTrace.encodeTraceMessage(SdlTrace.getBaseTicsDelta(), Mod.proto, frameDirection, protoMsg.toString());
+ return writeXmlTraceMessage(xml);
+ }
+
+ private static String getProtocolFrameType(FrameType f) {
+ if (f == FrameType.Control)
+ return "Control";
+ else if (f == FrameType.Consecutive)
+ return "Consecutive";
+ else if (f == FrameType.First)
+ return "First";
+ else if (f == FrameType.Single)
+ return "Single";
+
+ return "Unknown";
+ } // end-method
+
+ private static String getProtocolSessionType(SessionType serviceType) {
+ String s;
+ if (serviceType == SessionType.RPC )
+ s = "rpc";
+ else if (serviceType == SessionType.BULK_DATA)
+ s = "bulk";
+ else
+ s = "Unknown";
+ return s;
+ } // end-method
+
+ private static String getProtocolFrameHeaderInfo(SdlPacket hdr) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("<hdr>");
+ sb.append("<ver>");
+ sb.append(hdr.getVersion());
+ sb.append("</ver><cmp>");
+ sb.append(hdr.isEncrypted());
+ sb.append("</cmp><ft>");
+ sb.append(getProtocolFrameType(hdr.getFrameType()));
+ sb.append("</ft><st>");
+ sb.append(getProtocolSessionType(SessionType.valueOf((byte)hdr.getServiceType())));
+ sb.append("</st><sid>");
+ sb.append(hdr.getSessionId());
+ sb.append("</sid><sz>");
+ sb.append(hdr.getDataSize());
+ sb.append("</sz>");
+
+ int frameData = hdr.getFrameInfo();
+ if (hdr.getFrameType() == FrameType.Control) {
+ sb.append("<ca>");
+ if (frameData == FrameDataControlFrameType.StartSession.getValue())
+ sb.append("StartSession");
+ else if (frameData == FrameDataControlFrameType.StartSessionACK.getValue())
+ sb.append("StartSessionACK");
+ else if (frameData == FrameDataControlFrameType.StartSessionNACK.getValue())
+ sb.append("StartSessionNACK");
+ else if (frameData == FrameDataControlFrameType.EndSession.getValue())
+ sb.append("EndSession");
+ sb.append("</ca>");
+ } else if (hdr.getFrameType() == FrameType.Consecutive ) {
+ sb.append("<fsn>");
+ if (frameData == 0 )
+ sb.append("lastFrame");
+ else
+ sb.append(String.format("%02X",frameData));
+ sb.append("</fsn>");
+ } else if (hdr.getFrameType() == FrameType.First ) {
+ int totalSize = BitConverter.intFromByteArray(hdr.getPayload(), 0);
+ int numFrames = BitConverter.intFromByteArray(hdr.getPayload(), 4);
+ sb.append("<total>" + totalSize + "</total><numframes>" + numFrames + "</numframes>");
+ } else if (hdr.getFrameType() == FrameType.Single ) {
+ sb.append("<single/>");
+ }
+
+ sb.append("</hdr>");
+
+ return sb.toString();
+ } // end-method
+
+ public static String getBTDeviceInfo(Object btDevice) { //FIXME
+ StringBuilder sb = new StringBuilder();
+ /* FIXME sb.append("<btp>");
+ String btdn = btDevice.getName();
+ sb.append("<btn>");
+ sb.append(SdlTrace.B64EncodeForXML(btdn));
+ sb.append("</btn>");
+ sb.append("<bta>" + btDevice.getAddress() + "</bta>");
+ sb.append("<bts>" + btDevice.getBondState() + "</bts>");
+ sb.append("</btp>");*/
+ return sb.toString();
+ } // end-method
+
+ public static boolean logTransportEvent(String preamble, String transportSpecificInfoXml, InterfaceActivityDirection msgDirection, byte buf[], int byteLength, String token) {
+ return logTransportEvent(preamble, transportSpecificInfoXml, msgDirection, buf, 0, byteLength, token);
+ }
+
+ private static void checkB64(String x, byte[] buf, int offset, int byteLength) {
+ if ((x.length() % 4) != 0) {
+ NativeLogTool.logWarning(SdlTrace.SYSTEM_LOG_TAG, "b64 string length (" + x.length() + ") isn't multiple of 4: buf.length=" + buf.length + ", offset=" + offset + ", len=" + byteLength);
+ } // end-if
+ } // end-method
+
+ public static boolean logTransportEvent(String preamble, String transportSpecificInfoXml, InterfaceActivityDirection msgDirection, byte buf[], int offset, int byteLength, String token) {
+ if (DiagLevel.getLevel(Mod.tran) == DetailLevel.OFF || !token.equals(SDL_LIB_TRACE_KEY)) {
+ return false;
+ }
+
+ StringBuilder msg = new StringBuilder();
+ if (transportSpecificInfoXml != null && transportSpecificInfoXml.length() > 0) {
+ msg.append(transportSpecificInfoXml);
+ }
+ if (preamble != null && preamble.length() > 0) {
+ msg.append("<desc>");
+ msg.append(preamble);
+ msg.append("</desc>");
+ }
+ if (buf != null) {
+ msg.append("<sz>");
+ msg.append(byteLength);
+ msg.append("</sz>");
+ DetailLevel dl = DiagLevel.getLevel(Mod.tran);
+ if (dl == DetailLevel.VERBOSE) {
+ if (buf != null && byteLength > 0) {
+ msg.append("<d>");
+ String bytesInfo = Mime.base64Encode(buf, offset, byteLength);
+ checkB64(bytesInfo, buf, offset, byteLength);
+ msg.append(bytesInfo);
+ msg.append("</d>");
+ }
+ }
+ }
+ String xml = SdlTrace.encodeTraceMessage(SdlTrace.getBaseTicsDelta(), Mod.tran, msgDirection, msg.toString());
+ return writeXmlTraceMessage(xml);
+ }
+
+ // Package-scoped
+ static long getBaseTicsDelta() {
+ return System.currentTimeMillis() - getBaseTics();
+ }
+
+ // Package-scoped
+ static long getBaseTics() {
+ return baseTics;
+ } // end-method
+
+ public static Boolean writeMessageToSiphonServer(String info) {
+ return SiphonServer.sendFormattedTraceMessage(info);
+ }
+
+ private static boolean writeXmlTraceMessage(String msg) {
+ try {
+ // Attempt to write formatted message to the Siphon
+ if (false == writeMessageToSiphonServer(msg)) {
+ // If writing to the Siphon fails, write to the native log
+ NativeLogTool.logInfo(SdlTrace.SYSTEM_LOG_TAG, msg);
+ return false;
+ }
+
+ ISTListener localTraceListener = m_appTraceListener;
+
+ if (localTraceListener != null) {
+ try {
+ localTraceListener.logXmlMsg(msg, SDL_LIB_TRACE_KEY);
+ } catch (Exception ex) {
+ DebugTool.logError("Failure calling ISTListener: " + ex.toString(), ex);
+ return false;
+ }
+ }
+ } catch (Exception ex) {
+ NativeLogTool.logError(SdlTrace.SYSTEM_LOG_TAG, "Failure writing XML trace message: " + ex.toString());
+ return false;
+ }
+ return true;
+ }
+
+ // Package-scoped
+ @SuppressWarnings("deprecation")
+ public static String getLogHeader(String dumpReason, int seqNo) {
+ final String Sep = "-";
+ StringBuilder write = new StringBuilder("<?xml version=\"1.0\"?>" + "<logs>");
+ write.append("<info>");
+ StringBuilder infoBlock = new StringBuilder();
+ //FIXME
+ //String hostInfo = Build.BRAND + Sep + Build.MANUFACTURER + Sep + Build.MODEL + "(" + Build.HOST + ")";
+ //infoBlock.append("<host>" + SdlTrace.B64EncodeForXML(hostInfo) + "</host>");
+ //String osv = Build.VERSION.RELEASE + " (" + Build.VERSION.CODENAME + ")";
+ //infoBlock.append("<osv>" + SdlTrace.B64EncodeForXML(osv) + "</osv>");
+ //infoBlock.append(TraceDeviceInfo.getTelephonyHeader());
+
+ //long heapSize = Debug.getNativeHeapFreeSize() / 1024;
+ //long heapAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+ //infoBlock.append("<mem><hf>" + heapSize + "KB</hf><ha>" + heapAllocated + "KB</ha></mem>");
+ //infoBlock.append("<np>" + Runtime.getRuntime().availableProcessors() + "</np>");
+ //infoBlock.append("<pid>" + Process.myPid() + "</pid>");
+ //infoBlock.append("<tid>" + Thread.currentThread().getId() + "</tid>");
+
+ // String dateStamp = (String)
+ // DateFormat.format("yy-MM-dd hh:mm:ss SSS", new Timestamp(baseTics));
+ Timestamp stamp = new Timestamp(SdlTrace.getBaseTics());
+ String GMTtime = stamp.toGMTString().substring(0, 19);
+ long fracSec = stamp.getNanos() / 1000000; // divide by a million
+ String fracSecStr = String.format("%03d", fracSec);
+ infoBlock.append("<utc>" + GMTtime + "." + fracSecStr + "</utc>");
+
+ //infoBlock.append(TraceDeviceInfo.getLogHeaderBluetoothPairs());
+ infoBlock.append(getSmartDeviceLinkTraceRoot(dumpReason, seqNo));
+
+ write.append(infoBlock);
+
+ write.append("</info>" + "<msgs>");
+ return write.toString();
+ } // end-method
+
+ private static String getSmartDeviceLinkTraceRoot(String dumpReason, int seqNo) {
+ StringBuilder write = new StringBuilder("<SmartDeviceLinktraceroot>" + "<sequencenum>" + seqNo
+ + "</sequencenum>" + "<dumpreason>" + dumpReason
+ + "</dumpreason><tracelevel>");
+
+ write.append("<tran>" + DiagLevel.getLevel(Mod.tran) + "</tran>");
+ write.append("<proto>" + DiagLevel.getLevel(Mod.proto) + "</proto>");
+ write.append("<mar>" + DiagLevel.getLevel(Mod.mar) + "</mar>");
+ write.append("<rpc>" + DiagLevel.getLevel(Mod.rpc) + "</rpc>");
+ write.append("<proxy>" + DiagLevel.getLevel(Mod.proxy) + "</proxy>");
+ write.append("<app>" + DiagLevel.getLevel(Mod.app) + "</app>");
+
+ write.append("</tracelevel>");
+ write.append("</SmartDeviceLinktraceroot>");
+ return write.toString();
+ } // end-method
+} // end-class \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/trace/enums/DetailLevel.java b/base/src/main/java/com/smartdevicelink/trace/enums/DetailLevel.java
new file mode 100644
index 000000000..841711ff4
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/enums/DetailLevel.java
@@ -0,0 +1,16 @@
+package com.smartdevicelink.trace.enums;
+
+
+public enum DetailLevel {
+ OFF,
+ TERSE,
+ VERBOSE;
+
+ public static DetailLevel valueForString(String value) {
+ try{
+ return valueOf(value);
+ }catch(Exception e){
+ return null;
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/trace/enums/InterfaceActivityDirection.java b/base/src/main/java/com/smartdevicelink/trace/enums/InterfaceActivityDirection.java
new file mode 100644
index 000000000..cef1532e5
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/enums/InterfaceActivityDirection.java
@@ -0,0 +1,15 @@
+package com.smartdevicelink.trace.enums;
+
+public enum InterfaceActivityDirection {
+ Transmit,
+ Receive,
+ None;
+
+ public static InterfaceActivityDirection valueForString(String value) {
+ try{
+ return valueOf(value);
+ }catch(Exception e){
+ return null;
+ }
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/trace/enums/Mod.java b/base/src/main/java/com/smartdevicelink/trace/enums/Mod.java
new file mode 100644
index 000000000..330c3b616
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/trace/enums/Mod.java
@@ -0,0 +1,18 @@
+package com.smartdevicelink.trace.enums;
+
+public enum Mod {
+ tran,
+ proto,
+ mar,
+ rpc,
+ app,
+ proxy;
+
+ public static Mod valueForString(String value) {
+ try{
+ return valueOf(value);
+ }catch(Exception e){
+ return null;
+ }
+ }
+}; \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/transport/BaseTransportConfig.java b/base/src/main/java/com/smartdevicelink/transport/BaseTransportConfig.java
new file mode 100644
index 000000000..939a18bcf
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/BaseTransportConfig.java
@@ -0,0 +1,35 @@
+package com.smartdevicelink.transport;
+
+import com.smartdevicelink.transport.enums.TransportType;
+
+/**
+ * Defines base abstract class for transport configurations.
+ */
+public abstract class BaseTransportConfig {
+
+ protected boolean shareConnection = true;
+ protected int iHeartBeatTimeout = Integer.MAX_VALUE;
+ /**
+ * Gets transport type for this transport configuration.
+ *
+ * @return One of {@link TransportType} enumeration values that represents type of this transport configuration.
+ */
+ public abstract TransportType getTransportType();
+
+ /**
+ * Indicate whether the application want to share connection with others.
+ *
+ * @return
+ */
+ public boolean shareConnection() {
+ return shareConnection;
+ }
+
+ public int getHeartBeatTimeout() {
+ return iHeartBeatTimeout;
+ }
+
+ public void setHeartBeatTimeout(int iTimeout) {
+ iHeartBeatTimeout = iTimeout;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/ITransportListener.java b/base/src/main/java/com/smartdevicelink/transport/ITransportListener.java
new file mode 100644
index 000000000..b9ea2cf30
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/ITransportListener.java
@@ -0,0 +1,17 @@
+package com.smartdevicelink.transport;
+
+import com.smartdevicelink.protocol.SdlPacket;
+
+public interface ITransportListener {
+ // Called to indicate and deliver a packet received from transport
+ void onTransportPacketReceived(SdlPacket packet);
+
+ // Called to indicate that transport connection was established
+ void onTransportConnected();
+
+ // Called to indicate that transport was disconnected (by either side)
+ void onTransportDisconnected(String info);
+
+ // Called to indicate that some error occurred on the transport
+ void onTransportError(String info, Exception e);
+} // end-interface \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/transport/IpcSocketServer.java b/base/src/main/java/com/smartdevicelink/transport/IpcSocketServer.java
new file mode 100644
index 000000000..fc2698c45
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/IpcSocketServer.java
@@ -0,0 +1,63 @@
+package com.smartdevicelink.transport;
+
+import android.util.Log;
+
+import org.java_websocket.WebSocket;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.server.WebSocketServer;
+
+import java.net.InetSocketAddress;
+import java.security.MessageDigest;
+
+/**
+ * Created by Joey Grover on 1/23/18.
+ */
+
+public class IpcSocketServer extends WebSocketServer {
+ private static final String TAG = "IpcSocketServer";
+ byte[] key;
+ final String packageName;
+
+ public IpcSocketServer(String packageName){
+ super((new InetSocketAddress(1985)));
+ this.packageName = packageName;
+ try{
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ md.update((System.currentTimeMillis()+"").getBytes());
+ key = md.digest();
+ Log.d(TAG, "Key: " + key);
+ }catch (Exception e){
+
+ }
+ }
+
+ @Override
+ public void onOpen(WebSocket conn, ClientHandshake handshake) {
+ Log.d(TAG, "onOpen");
+ conn.send(packageName);
+ }
+
+ @Override
+ public void onClose(WebSocket conn, int code, String reason, boolean remote) {
+ Log.d(TAG, "onClose");
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, String message) {
+ Log.d(TAG, "onMessage: " + message);
+ }
+
+ @Override
+ public void onError(WebSocket conn, Exception ex) {
+ Log.d(TAG, "onError");
+ }
+
+ @Override
+ public void onStart() {
+ Log.d(TAG, "onStart");
+ }
+
+ public byte[] getKey(){
+ return key;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/SdlPsm.java b/base/src/main/java/com/smartdevicelink/transport/SdlPsm.java
new file mode 100644
index 000000000..f5b650722
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/SdlPsm.java
@@ -0,0 +1,261 @@
+package com.smartdevicelink.transport;
+
+import com.smartdevicelink.protocol.SdlPacket;
+
+import static com.smartdevicelink.protocol.SdlProtocol.V1_HEADER_SIZE;
+import static com.smartdevicelink.protocol.SdlProtocol.V1_V2_MTU_SIZE;
+
+
+public class SdlPsm{
+ //private static final String TAG = "Sdl PSM";
+ //Each state represents the byte that should be incomming
+
+ public static final int START_STATE = 0x0;
+ public static final int SERVICE_TYPE_STATE = 0x02;
+ public static final int CONTROL_FRAME_INFO_STATE = 0x03;
+ public static final int SESSION_ID_STATE = 0x04;
+ public static final int DATA_SIZE_1_STATE = 0x05;
+ public static final int DATA_SIZE_2_STATE = 0x06;
+ public static final int DATA_SIZE_3_STATE = 0x07;
+ public static final int DATA_SIZE_4_STATE = 0x08;
+ public static final int MESSAGE_1_STATE = 0x09;
+ public static final int MESSAGE_2_STATE = 0x0A;
+ public static final int MESSAGE_3_STATE = 0x0B;
+ public static final int MESSAGE_4_STATE = 0x0C;
+ public static final int DATA_PUMP_STATE = 0x0D;
+ public static final int FINISHED_STATE = 0xFF;
+ public static final int ERROR_STATE = -1;
+
+
+ private static final byte FIRST_FRAME_DATA_SIZE = 0x08;
+
+ private static final int VERSION_MASK = 0xF0; //4 highest bits
+ private static final int COMPRESSION_MASK = 0x08; //4th lowest bit
+ private static final int FRAME_TYPE_MASK = 0x07; //3 lowest bits
+
+
+
+ int state ;
+
+ int version;
+ boolean compression;
+ int frameType;
+ int serviceType;
+ int controlFrameInfo;
+ int sessionId;
+ int dumpSize, dataLength;
+ int messageId = 0;
+
+ byte[] payload;
+
+ public SdlPsm(){
+ reset();
+ }
+
+ public boolean handleByte(byte data) {
+ //Log.trace(TAG, data + " = incomming");
+ state = transitionOnInput(data,state);
+
+ if(state==ERROR_STATE){
+ return false;
+ }
+ return true;
+ }
+
+ private int transitionOnInput(byte rawByte, int state){
+ switch(state){
+ case START_STATE:
+ version = (rawByte&(byte)VERSION_MASK)>>4;
+ //Log.trace(TAG, "Version: " + version);
+ if(version==0){ //It should never be 0
+ return ERROR_STATE;
+ }
+ compression = (1 == ((rawByte&(byte)COMPRESSION_MASK)>>3));
+
+
+ frameType = rawByte&(byte)FRAME_TYPE_MASK;
+ //Log.trace(TAG, rawByte + " = Frame Type: " + frameType);
+
+ if((version < 1 || version > 5) //These are known versions supported by this library.
+ && frameType!=SdlPacket.FRAME_TYPE_CONTROL){
+ return ERROR_STATE;
+ }
+
+ if(frameType<SdlPacket.FRAME_TYPE_CONTROL || frameType > SdlPacket.FRAME_TYPE_CONSECUTIVE){
+ return ERROR_STATE;
+ }
+
+ return SERVICE_TYPE_STATE;
+
+ case SERVICE_TYPE_STATE:
+ serviceType = (int)(rawByte&0xFF);
+ return CONTROL_FRAME_INFO_STATE;
+
+ case CONTROL_FRAME_INFO_STATE:
+ controlFrameInfo = (int)(rawByte&0xFF);
+ //Log.trace(TAG,"Frame Info: " + controlFrameInfo);
+ switch(frameType){
+ case SdlPacket.FRAME_TYPE_CONTROL:
+ /*if(frameInfo<FRAME_INFO_HEART_BEAT
+ || (frameInfo>FRAME_INFO_END_SERVICE_ACK
+ && (frameInfo!=FRAME_INFO_SERVICE_DATA_ACK || frameInfo!=FRAME_INFO_HEART_BEAT_ACK))){
+ return ERROR_STATE;
+ }*/ //Although some bits are reserved...whatever
+ break;
+ case SdlPacket.FRAME_TYPE_SINGLE: //Fall through since they are both the same
+ case SdlPacket.FRAME_TYPE_FIRST:
+ if(controlFrameInfo!=0x00){
+ return ERROR_STATE;
+ }
+ break;
+ case SdlPacket.FRAME_TYPE_CONSECUTIVE:
+ //It might be a good idea to check packet sequence numbers here
+ break;
+
+ default:
+ return ERROR_STATE;
+ }
+ return SESSION_ID_STATE;
+
+ case SESSION_ID_STATE:
+ sessionId = (int)(rawByte&0xFF);
+ return DATA_SIZE_1_STATE;
+
+ case DATA_SIZE_1_STATE:
+ //First data size byte
+ //Log.d(TAG, "Data byte 1: " + rawByte);
+ dataLength += ((int)(rawByte& 0xFF))<<24; //3 bytes x 8 bits
+ //Log.d(TAG, "Data Size 1 : " + dataLength);
+ return DATA_SIZE_2_STATE;
+
+ case DATA_SIZE_2_STATE:
+ //Log.d(TAG, "Data byte 2: " + rawByte);
+ dataLength += ((int)(rawByte& 0xFF))<<16; //2 bytes x 8 bits
+ //Log.d(TAG, "Data Size 2 : " + dataLength);
+ return DATA_SIZE_3_STATE;
+
+ case DATA_SIZE_3_STATE:
+ //Log.d(TAG, "Data byte 3: " + rawByte);
+ dataLength += ((int)(rawByte& 0xFF))<<8; //1 byte x 8 bits
+ //Log.d(TAG, "Data Size 3 : " + dataLength);
+ return DATA_SIZE_4_STATE;
+
+ case DATA_SIZE_4_STATE:
+ //Log.d(TAG, "Data byte 4: " + rawByte);
+ dataLength+=((int)rawByte) & 0xFF;
+ //Log.trace(TAG, "Data Size: " + dataLength);
+ //We should have data length now for the pump state
+ switch(frameType){ //If all is correct we should break out of this switch statement
+ case SdlPacket.FRAME_TYPE_SINGLE:
+ case SdlPacket.FRAME_TYPE_CONSECUTIVE:
+ break;
+ case SdlPacket.FRAME_TYPE_CONTROL:
+ //Ok, well here's some interesting bit of knowledge. Because the start session request is from the phone with no knowledge of version it sends out
+ //a v1 packet. THEREFORE there is no message id field. **** Now you know and knowing is half the battle ****
+ if(version==1 && controlFrameInfo == SdlPacket.FRAME_INFO_START_SERVICE){
+ if(dataLength==0){
+ return FINISHED_STATE; //We are done if we don't have any payload
+ }
+ if(dataLength <= V1_V2_MTU_SIZE - V1_HEADER_SIZE){ // sizes from protocol/WiProProtocol.java
+ payload = new byte[dataLength];
+ }else{
+ return ERROR_STATE;
+ }
+ dumpSize = dataLength;
+ return DATA_PUMP_STATE;
+ }
+ break;
+
+ case SdlPacket.FRAME_TYPE_FIRST:
+ if(dataLength==FIRST_FRAME_DATA_SIZE){
+ break;
+ }
+ default:
+ return ERROR_STATE;
+ }
+ if(version==1){ //Version 1 packets will not have message id's
+ if(dataLength == 0){
+ return FINISHED_STATE; //We are done if we don't have any payload
+ }
+ if(dataLength <= V1_V2_MTU_SIZE - V1_HEADER_SIZE){ // sizes from protocol/WiProProtocol.java
+ payload = new byte[dataLength];
+ }else{
+ return ERROR_STATE;
+ }
+ dumpSize = dataLength;
+ return DATA_PUMP_STATE;
+ }else{
+ return MESSAGE_1_STATE;
+ }
+
+ case MESSAGE_1_STATE:
+ messageId += ((int)(rawByte& 0xFF))<<24; //3 bytes x 8 bits
+ return MESSAGE_2_STATE;
+
+ case MESSAGE_2_STATE:
+ messageId += ((int)(rawByte& 0xFF))<<16; //2 bytes x 8 bits
+ return MESSAGE_3_STATE;
+
+ case MESSAGE_3_STATE:
+ messageId += ((int)(rawByte& 0xFF))<<8; //1 byte x 8 bits
+ return MESSAGE_4_STATE;
+
+ case MESSAGE_4_STATE:
+ messageId+=((int)rawByte) & 0xFF;
+
+ if(dataLength==0){
+ return FINISHED_STATE; //We are done if we don't have any payload
+ }
+ try{
+ payload = new byte[dataLength];
+ }catch(OutOfMemoryError oom){
+ return ERROR_STATE;
+ }
+ dumpSize = dataLength;
+ return DATA_PUMP_STATE;
+
+ case DATA_PUMP_STATE:
+ payload[dataLength-dumpSize] = rawByte;
+ dumpSize--;
+ //Do we have any more bytes to read in?
+ if(dumpSize>0){
+ return DATA_PUMP_STATE;
+ }
+ else if(dumpSize==0){
+ return FINISHED_STATE;
+ }else{
+ return ERROR_STATE;
+ }
+ case FINISHED_STATE: //We shouldn't be here...Should have been reset
+ default:
+ return ERROR_STATE;
+
+ }
+
+ }
+
+ public SdlPacket getFormedPacket(){
+ if(state==FINISHED_STATE){
+ //Log.trace(TAG, "Finished packet.");
+ return new SdlPacket(version, compression, frameType,
+ serviceType, controlFrameInfo, sessionId,
+ dataLength, messageId, payload);
+ }else{
+ return null;
+ }
+ }
+
+ public int getState() {
+ return state;
+ }
+
+ public void reset() {
+ version = 0;
+ state = START_STATE;
+ messageId = 0;
+ dataLength = 0;
+ frameType = 0x00; //Set it to null
+ payload = null;
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/SdlTransport.java b/base/src/main/java/com/smartdevicelink/transport/SdlTransport.java
new file mode 100644
index 000000000..21429841f
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/SdlTransport.java
@@ -0,0 +1,122 @@
+package com.smartdevicelink.transport;
+
+import com.smartdevicelink.exception.SdlException;
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.trace.SdlTrace;
+import com.smartdevicelink.trace.enums.InterfaceActivityDirection;
+import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.util.DebugTool;
+
+public abstract class SdlTransport {
+ private static final String SDL_LIB_TRACE_KEY = "42baba60-eb57-11df-98cf-0800200c9a66";
+
+ private final static String FailurePropagating_Msg = "Failure propagating ";
+ private Boolean isConnected = false;
+
+ private String _sendLockObj = "lock";
+
+
+ // Get status of transport connection
+ public Boolean getIsConnected() {
+ return isConnected;
+ }
+
+ //protected SdlTransport(String endpointName, String param2, ITransportListener transportListener)
+ protected SdlTransport(ITransportListener transportListener) {
+ if (transportListener == null) {
+ throw new IllegalArgumentException("Provided transport listener interface reference is null");
+ } // end-if
+ _transportListener = transportListener;
+ } // end-method
+
+ // This method is called by the subclass to indicate that data has arrived from
+ // the transport.
+ protected void handleReceivedPacket(SdlPacket packet) {
+ try {
+ // Trace received data
+ if (packet!=null) {
+ // Send transport data to the siphon server
+ //FIXME SiphonServer.sendBytesFromSDL(receivedBytes, 0, receivedBytesLength);
+ //SdlTrace.logTransportEvent("", null, InterfaceActivityDirection.Receive, receivedBytes, receivedBytesLength, SDL_LIB_TRACE_KEY);
+
+ _transportListener.onTransportPacketReceived(packet);
+ } // end-if
+ } catch (Exception excp) {
+ DebugTool.logError(FailurePropagating_Msg + "handleBytesFromTransport: " + excp.toString(), excp);
+ handleTransportError(FailurePropagating_Msg, excp);
+ } // end-catch
+ } // end-method
+
+ // This method must be implemented by transport subclass, and is called by this
+ // base class to actually send an array of bytes out over the transport. This
+ // method is meant to only be callable within the class hierarchy.
+ protected abstract boolean sendBytesOverTransport(SdlPacket packet);
+
+ // This method is called by whomever has reference to transport to have bytes
+ // sent out over transport.
+ /* public boolean sendBytes(byte[] message) {
+ return sendBytes(message, 0, message.length);
+ }*/ // end-method
+
+ // This method is called by whomever has reference to transport to have bytes
+ // sent out over transport.
+ public boolean sendBytes(SdlPacket packet) {
+ boolean bytesWereSent = false;
+ synchronized (_sendLockObj) {
+ bytesWereSent = sendBytesOverTransport(packet);//message, offset, length);
+ } // end-lock
+ // Send transport data to the siphon server
+ //FIXME SiphonServer.sendBytesFromAPP(message, offset, length);
+
+ //FIXME SdlTrace.logTransportEvent("", null, InterfaceActivityDirection.Transmit, message, offset, length, SDL_LIB_TRACE_KEY);
+ return bytesWereSent;
+ } // end-method
+
+ private ITransportListener _transportListener = null;
+
+ // This method is called by the subclass to indicate that transport connection
+ // has been established.
+ protected void handleTransportConnected() {
+ isConnected = true;
+ try {
+ SdlTrace.logTransportEvent("Transport.connected", null, InterfaceActivityDirection.Receive, null, 0, SDL_LIB_TRACE_KEY);
+ _transportListener.onTransportConnected();
+ } catch (Exception excp) {
+ DebugTool.logError(FailurePropagating_Msg + "onTransportConnected: " + excp.toString(), excp);
+ handleTransportError(FailurePropagating_Msg + "onTransportConnected", excp);
+ } // end-catch
+ } // end-method
+
+ // This method is called by the subclass to indicate that transport disconnection
+ // has occurred.
+ protected void handleTransportDisconnected(final String info) {
+ isConnected = false;
+
+ try {
+ SdlTrace.logTransportEvent("Transport.disconnect: " + info, null, InterfaceActivityDirection.Transmit, null, 0, SDL_LIB_TRACE_KEY);
+ _transportListener.onTransportDisconnected(info);
+ } catch (Exception excp) {
+ DebugTool.logError(FailurePropagating_Msg + "onTransportDisconnected: " + excp.toString(), excp);
+ } // end-catch
+ } // end-method
+
+ // This method is called by the subclass to indicate a transport error has occurred.
+ protected void handleTransportError(final String message, final Exception ex) {
+ isConnected = false;
+ _transportListener.onTransportError(message, ex);
+ }
+
+ public abstract void openConnection() throws SdlException;
+ public abstract void disconnect();
+
+ /**
+ * Abstract method which should be implemented by subclasses in order to return actual type of the transport.
+ *
+ * @return One of {@link TransportType} enumeration values.
+ *
+ * @see TransportType
+ */
+ public abstract TransportType getTransportType();
+
+ public abstract String getBroadcastComment();
+} // end-class
diff --git a/base/src/main/java/com/smartdevicelink/transport/SiphonServer.java b/base/src/main/java/com/smartdevicelink/transport/SiphonServer.java
new file mode 100644
index 000000000..68107f407
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/SiphonServer.java
@@ -0,0 +1,384 @@
+package com.smartdevicelink.transport;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.BindException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+import com.smartdevicelink.util.*;
+
+public class SiphonServer {
+ // Prohibit use of no-arg ctor
+ private SiphonServer() {}
+
+ enum SiphonDataType {
+ fromSdl,
+ fromApp,
+ appLog,
+ formattedTrace,
+ baselineTimeStamp,
+ traceSettings
+ }
+
+ // Boolean to enable/disable the siphon
+ private static Boolean m_siphonEnabled = false;
+
+ // Boolean to determine if the siphon has been initialized
+ private static Boolean m_siphonInitialized = false;
+ private static Boolean m_foundOpenSocket = false;
+ private static Socket m_siphonSocket = null;
+ private static Object m_siphonLock = new Object();
+ private static ServerSocket m_listeningSocket = null;
+ private static short m_listenPort = -1;
+ private static OutputStream m_siphonSocketOutputStream = null;
+ private static SiphonServerThread m_siphonClientThread = null;
+
+ // Initial timestamp in MS
+ private static long m_startTimeStamp = 0;
+
+ // SDL Trace Message Version
+ private static byte m_sdlTraceMsgVersionNumber = 1;
+
+ // Max number of ports to attempt a connection on
+ private final static Integer MAX_NUMBER_OF_PORT_ATTEMPTS = 20;
+
+ // Starting port for future port attempts
+ private final static short FIRST_PORT_TO_ATTEMPT_CONNECTION = 7474;
+
+ // Boolean to determine if formatted trace is being sent
+ private static Boolean m_sendingFormattedTrace = false;
+
+ public static short enableSiphonServer() {
+ m_siphonEnabled = true;
+ SiphonServer.init();
+ return m_listenPort;
+ }
+
+ public static Boolean getSiphonEnabledStatus() {
+ return m_siphonEnabled;
+ }
+
+ public static short disableSiphonServer() {
+ if (!m_siphonEnabled) {
+ m_listenPort = -1;
+ } else {
+ m_siphonEnabled = false;
+ }
+
+ m_siphonInitialized = false;
+
+ try {
+ SiphonServer.closeServer();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return m_listenPort;
+ }
+
+ public static boolean init() {
+ // Only initialize if the siphon has not been initialized previously
+
+ // Check here to be lean. If true, no need to sdlhronize
+ if (m_siphonInitialized) {
+ return true;
+ }
+
+ synchronized (m_siphonLock) {
+ // To prevent a race condition, re-check m_siphonInitialized inside of sdlhronize block
+ if (!m_siphonInitialized) {
+ if (m_siphonClientThread == null) {
+ // Set current time stamp
+ m_startTimeStamp = System.currentTimeMillis();
+
+ // Start Siphon Thread
+ m_siphonClientThread = new SiphonServerThread();
+ m_siphonClientThread.setName("Siphon");
+ m_siphonClientThread.setDaemon(true);
+ m_foundOpenSocket = m_siphonClientThread.findOpenSocket(FIRST_PORT_TO_ATTEMPT_CONNECTION);
+ m_siphonClientThread.start();
+
+ m_siphonInitialized = true;
+ } // end-if
+ } // end-lock
+ }
+
+ return m_siphonInitialized;
+ } // end-method
+
+ public static void closeServer() throws IOException {
+
+ if (m_siphonClientThread != null) {
+ m_siphonClientThread.halt();
+ m_siphonClientThread = null;
+ }
+
+ if (m_listeningSocket != null) {
+ m_listeningSocket.close();
+ m_listeningSocket = null;
+ }
+
+ if (m_siphonSocket != null) {
+ m_siphonSocket.close();
+ m_siphonSocket = null;
+ }
+
+ if (m_siphonSocketOutputStream != null) {
+ m_siphonSocketOutputStream.close();
+ m_siphonSocketOutputStream = null;
+ }
+ }
+
+ public static Boolean sendBytesFromAPP(byte[] msgBytes, int offset, int length) {
+
+ if (m_sendingFormattedTrace) {
+ return false;
+ }
+
+ return sendSiphonData(SiphonDataType.fromApp, msgBytes, offset, length);
+ } // end-method
+
+ public static Boolean sendBytesFromSDL(byte[] msgBytes, int offset, int length) {
+
+ if (m_sendingFormattedTrace) {
+ return false;
+ }
+
+ return sendSiphonData(SiphonDataType.fromSdl, msgBytes, offset, length);
+ } // end-method
+
+ public static Boolean sendSiphonLogData(String message) {
+
+ if (m_sendingFormattedTrace) {
+ return false;
+ }
+
+ if (message == null || message.length() == 0) {
+ return false;
+ }
+
+ byte messageBytes[] = null;
+ int messageLength = 0;
+
+ try {
+ messageBytes = message.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return false;
+ }
+
+ messageLength = messageBytes.length;
+ return sendSiphonData(SiphonDataType.appLog, messageBytes, 0, messageLength);
+
+ }
+
+ public static Boolean sendFormattedTraceMessage(String message) {
+
+ if (message == null || message.length() == 0) {
+ return false;
+ }
+
+ byte messageBytes[] = null;
+ int messageLength = 0;
+
+ try {
+ messageBytes = message.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return false;
+ }
+
+ messageLength = messageBytes.length;
+ if (sendSiphonData(SiphonDataType.formattedTrace, messageBytes, 0, messageLength)) {
+ m_sendingFormattedTrace = true;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static Boolean sendSiphonData(SiphonDataType direction, byte[] msgBytes, int offset, int length) {
+ byte siphonDataTypeIndicator = 0x00;
+
+ long currentDateTime = System.currentTimeMillis();
+ Integer deltaTimeMills = null;
+
+ deltaTimeMills = (int)(currentDateTime - m_startTimeStamp);
+
+ switch(direction) {
+ case fromSdl:
+ siphonDataTypeIndicator = 0x00;
+ break;
+ case fromApp:
+ siphonDataTypeIndicator = 0x01;
+ break;
+ case appLog:
+ siphonDataTypeIndicator = 0x02;
+ break;
+ case formattedTrace:
+ siphonDataTypeIndicator = 0x03;
+ break;
+ case baselineTimeStamp:
+ siphonDataTypeIndicator = 0x04;
+ break;
+ case traceSettings:
+ siphonDataTypeIndicator = 0x05;
+ break;
+ default:
+ siphonDataTypeIndicator = 0x00;
+ break;
+ }
+
+ // Set high bit to indicate new format
+ siphonDataTypeIndicator = (byte)((byte)0x80 | siphonDataTypeIndicator);
+
+ return sendDataToSiphonSocket(siphonDataTypeIndicator, deltaTimeMills, msgBytes, offset, length);
+ }
+
+ private synchronized static Boolean sendDataToSiphonSocket(byte directionIndicator, Integer timeStamp,
+ byte[] msgBytes, int offset, int length) {
+ if (!m_siphonEnabled) {
+ return false;
+ }
+
+ if (msgBytes == null || length == 0) {
+ return false;
+ }
+
+ OutputStream siphonOutputStream = null;
+
+ synchronized (m_siphonLock) {
+ siphonOutputStream = m_siphonSocketOutputStream;
+ } // end-lock
+
+ if (siphonOutputStream == null) {
+ return false;
+ }
+
+ try {
+ // blobSize = length(of message) + 1(size of direction indicator)
+ // + 1 (size of msgVersionNumber) + 4 (size of timeStamp)
+ int blobSize = length + 1 + 1 + 4;
+
+ siphonOutputStream.write(BitConverter.intToByteArray(blobSize));
+ siphonOutputStream.write(new byte[] {directionIndicator});
+ siphonOutputStream.write(new byte[] {m_sdlTraceMsgVersionNumber});
+ siphonOutputStream.write(intToByteArray(timeStamp));
+ siphonOutputStream.write(msgBytes, offset, length);
+ } catch (Exception ex) {
+ return false;
+ } // end-catch
+
+ return true;
+ } // end-method
+
+ private static class SiphonServerThread extends Thread {
+
+ private Boolean isHalted = false;
+ short listenPort;
+
+ public void halt() {
+ isHalted = true;
+ }
+
+ private boolean findOpenSocket(short port) {
+ // Accept incoming sihpon connection from trace utility.
+ Boolean foundOpenPort = false;
+ listenPort = port;
+
+ // Listen to accept incoming connection from SDL
+ while (!foundOpenPort) {
+ try {
+ m_listeningSocket = new ServerSocket(listenPort);
+ foundOpenPort = true;
+ m_listenPort = listenPort;
+ } catch (BindException ex) {
+ listenPort++;
+ if(listenPort > port + MAX_NUMBER_OF_PORT_ATTEMPTS) {
+ return false;
+ }
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+ return foundOpenPort;
+ }
+
+ private void startServerOnPort() throws IOException {
+ Socket newSocket = null;
+
+ // Wait for a connection
+ newSocket = m_listeningSocket.accept();
+
+ // If isHalted after accept() delay, return
+ if (isHalted) {
+ return;
+ }
+
+ synchronized (m_siphonLock) {
+ // Reset siphonSocketOutputStream
+ if (m_siphonSocketOutputStream != null) {
+ try {
+ m_siphonSocketOutputStream.close();
+ } catch (IOException e) {
+ // Do nothing
+ }
+ m_siphonSocketOutputStream = null;
+ }
+
+ // Reset siphonSocket
+ if (m_siphonSocket != null) {
+ try {
+ m_siphonSocket.close();
+ } catch (IOException e) {
+ // Do nothing
+ }
+ m_siphonSocket = null;
+ }
+
+ // Store the new socket
+ m_siphonSocket = newSocket;
+
+ // Set Socket Options
+ m_siphonSocket.setKeepAlive(true);
+
+ // Get the output stream of the connection
+ m_siphonSocketOutputStream = m_siphonSocket.getOutputStream();
+
+ // Output version number to the Siphon upon connection (version number prepending to logInfo)
+ DebugTool.logInfo("Siphon connected.");
+ } // end-lock
+ } // end-method
+
+ @Override
+ public void run() {
+ try {
+ if (m_foundOpenSocket){
+ while (!isHalted) {
+ startServerOnPort();
+ }
+ }
+ } catch (Exception ex) {
+ // Do nothing
+ } finally {
+ if (m_listeningSocket != null) {
+ try {
+ m_listeningSocket.close();
+ } catch (IOException e) {
+ // Do nothing
+ }
+ m_listeningSocket = null;
+ }
+ }
+ }
+ }
+
+ private static final byte[] intToByteArray(int value) {
+ return new byte[] {
+ (byte)(value >>> 24),
+ (byte)(value >>> 16),
+ (byte)(value >>> 8),
+ (byte)value};
+ }
+} // end-class \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java b/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
new file mode 100644
index 000000000..a66651502
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/TransportConstants.java
@@ -0,0 +1,263 @@
+package com.smartdevicelink.transport;
+
+
+/**
+ * These constants are shared between the router service and the SDL base service.
+ * They are defined as strings/actions/values that both of them can understand.
+ * Attempting to use standard HTTP error codes as definitions.
+ * @author Joey Grover
+ *
+ */
+public class TransportConstants {
+ public static final String START_ROUTER_SERVICE_ACTION ="sdl.router.startservice";
+ public static final String ROUTER_SERVICE_ACTION = "com.smartdevicelink.router.service";
+ public static final String FOREGROUND_EXTRA = "foreground";
+
+ public static final String BIND_LOCATION_PACKAGE_NAME_EXTRA = "BIND_LOCATION_PACKAGE_NAME_EXTRA";
+ public static final String BIND_LOCATION_CLASS_NAME_EXTRA = "BIND_LOCATION_CLASS_NAME_EXTRA";
+
+ public static final String ALT_TRANSPORT_RECEIVER = "com.sdl.android.alttransport";
+ public static final String ALT_TRANSPORT_CONNECTION_STATUS_EXTRA = "connection_status";
+ public static final int ALT_TRANSPORT_DISCONNECTED = 0;
+ public static final int ALT_TRANSPORT_CONNECTED = 1;
+ public static final String ALT_TRANSPORT_READ = "read";//Read from the alt transport, goes to the app
+ public static final String ALT_TRANSPORT_WRITE = "write";//Write to the alt transport, comes from the app
+ public static final String ALT_TRANSPORT_ADDRESS_EXTRA = "altTransportAddress";
+
+ public static final String START_ROUTER_SERVICE_SDL_ENABLED_EXTRA = "sdl_enabled";
+ public static final String START_ROUTER_SERVICE_SDL_ENABLED_APP_PACKAGE = "package_name";
+ public static final String START_ROUTER_SERVICE_SDL_ENABLED_CMP_NAME = "component_name";
+ public static final String START_ROUTER_SERVICE_TRANSPORT_CONNECTED = "transport_connected"; //Extra for the transport that just connected
+ public static final String START_ROUTER_SERVICE_SDL_ENABLED_PING = "ping";
+ @Deprecated
+ public static final String FORCE_TRANSPORT_CONNECTED = "force_connect"; //This is legacy, do not refactor this.
+ public static final String ROUTER_SERVICE_VALIDATED = "router_service_validated";
+
+ @Deprecated
+ public static final String REPLY_TO_INTENT_EXTRA = "ReplyAddress";
+ public static final String CONNECT_AS_CLIENT_BOOLEAN_EXTRA = "connectAsClient";
+ public static final String PACKAGE_NAME_STRING = "package.name";
+ public static final String APP_ID_EXTRA = "app.id";//Sent as a Long. This is no longer used
+ public static final String APP_ID_EXTRA_STRING = "app.id.string";
+ public static final String ROUTER_MESSAGING_VERSION = "router.messaging.version";
+
+ public static final String SESSION_ID_EXTRA = "session.id";
+
+ public static final String ENABLE_LEGACY_MODE_EXTRA = "ENABLE_LEGACY_MODE_EXTRA";
+
+ @Deprecated
+ public static final String HARDWARE_DISCONNECTED = "hardware.disconect";
+ public static final String TRANSPORT_DISCONNECTED = "transport.disconect";
+ public static final String HARDWARE_CONNECTED = "hardware.connected";
+ public static final String CURRENT_HARDWARE_CONNECTED = "current.hardware.connected";
+
+ public static final String SEND_PACKET_TO_APP_LOCATION_EXTRA_NAME = "senderintent";
+ public static final String SEND_PACKET_TO_ROUTER_LOCATION_EXTRA_NAME = "routerintent";
+
+
+ public static final String BIND_REQUEST_TYPE_CLIENT = "BIND_REQUEST_TYPE_CLIENT";
+ public static final String BIND_REQUEST_TYPE_ALT_TRANSPORT = "BIND_REQUEST_TYPE_ALT_TRANSPORT";
+ public static final String BIND_REQUEST_TYPE_STATUS = "BIND_REQUEST_TYPE_STATUS";
+ public static final String BIND_REQUEST_TYPE_USB_PROVIDER = "BIND_REQUEST_TYPE_USB_PROVIDER";
+
+
+ public static final String PING_ROUTER_SERVICE_EXTRA = "ping.router.service";
+
+ public static final String SDL_NOTIFICATION_CHANNEL_ID = "sdl_notification_channel";
+ public static final String SDL_NOTIFICATION_CHANNEL_NAME = "SmartDeviceLink";
+
+
+
+
+ /**
+ * This class houses all important router service versions
+ */
+ public class RouterServiceVersions{
+ /**
+ * This version of the router service is when app IDs went from Longs to Strings
+ */
+ public static final int APPID_STRING = 4;
+ }
+
+
+ /*
+ * Alt transport
+ *
+ */
+
+ /**
+ * This will be the response when a hardware connect event comes through from an alt transport.
+ * This is because it only makes sense to register an alt transport when a connection is established with that
+ * transport, not waiting for one.
+ */
+ public static final int ROUTER_REGISTER_ALT_TRANSPORT_RESPONSE = 0x02;
+ public static final int ROUTER_REGISTER_ALT_TRANSPORT_RESPONSE_SUCESS = 0x00;
+ /**
+ * There is already another alt transport connected, so we are unable to register this one
+ */
+ public static final int ROUTER_REGISTER_ALT_TRANSPORT_ALREADY_CONNECTED = 0x01;
+
+ /**
+ * This means the router service is shutting down for some reason. Most likely
+ */
+ public static final int ROUTER_SHUTTING_DOWN_NOTIFICATION = 0x0F;
+
+ /**
+ * There is a newer service to start up, so this one is shutting down
+ */
+ public static final int ROUTER_SHUTTING_DOWN_REASON_NEWER_SERVICE = 0x00;
+
+ /*
+ * Router to Client binding service
+ *
+ */
+
+ //WHATS
+ /**
+ * Command to the service to register a client, receiving callbacks
+ * from the service. The Message's replyTo field must be a Messenger of
+ * the client where callbacks should be sent.
+ */
+ public static final int ROUTER_REGISTER_CLIENT = 0x01;
+ /**
+ * This response message will contain if the registration request was successful or not. If not, the reason will be
+ * great or equal to 1 and be descriptive of why it was denied.
+ */
+ public static final int ROUTER_REGISTER_CLIENT_RESPONSE = 0x02;
+ //Response arguments
+ public static final int REGISTRATION_RESPONSE_SUCESS = 0x00;
+ public static final int REGISTRATION_RESPONSE_DENIED_AUTHENTICATION_FAILED = 0x01;
+ public static final int REGISTRATION_RESPONSE_DENIED_NO_CONNECTION = 0x02;
+ public static final int REGISTRATION_RESPONSE_DENIED_APP_ID_NOT_INCLUDED = 0x03;
+ public static final int REGISTRATION_RESPONSE_DENIED_LEGACY_MODE_ENABLED = 0x04;
+ public static final int REGISTRATION_RESPONSE_DENIED_UNKNOWN = 0xFF;
+
+ /**
+ * Command to the service to unregister a client, to stop receiving callbacks
+ * from the service. The Message's replyTo field must be a Messenger of
+ * the client as previously given with MSG_REGISTER_CLIENT. Also include the app id as arg1.
+ */
+ public static final int ROUTER_UNREGISTER_CLIENT = 0x03;
+ public static final int ROUTER_UNREGISTER_CLIENT_RESPONSE = 0x04;
+ //Response arguments
+ public static final int UNREGISTRATION_RESPONSE_SUCESS = 0x00;
+ public static final int UNREGISTRATION_RESPONSE_FAILED_APP_ID_NOT_FOUND = 0x01;
+
+
+ /**
+ * what message type to notify apps of a hardware connection event. The connection event will be placed in the bundle
+ * attached to the message
+ */
+ public static final int HARDWARE_CONNECTION_EVENT = 0x05;
+ public static final int HARDWARE_CONNECTION_EVENT_CONNECTED = 0x10;
+ public static final int HARDWARE_CONNECTION_EVENT_DISCONNECTED = 0x30;
+
+
+ public static final int ROUTER_REQUEST_BT_CLIENT_CONNECT = 0x10;
+ public static final int ROUTER_REQUEST_BT_CLIENT_CONNECT_RESPONSE = 0x11;
+
+ /**
+ * This provides the app with an ability to request another session within the router service.
+ * A replyTo must be provided or else there won't be a response
+ */
+ public static final int ROUTER_REQUEST_NEW_SESSION = 0x12;
+ //Request arguments
+ //See TRANSPORT_TYPE & TRANSPORT_ADDRESS
+
+
+ public static final int ROUTER_REQUEST_NEW_SESSION_RESPONSE = 0x13;
+ //Response arguments
+ public static final int ROUTER_REQUEST_NEW_SESSION_RESPONSE_SUCESS = 0x00;
+ public static final int ROUTER_REQUEST_NEW_SESSION_RESPONSE_FAILED_APP_NOT_FOUND = 0x01;
+ public static final int ROUTER_REQUEST_NEW_SESSION_RESPONSE_FAILED_APP_ID_NOT_INCL = 0x02;
+
+ /**
+ * This provides the app with an ability to request another session within the router service.
+ * A replyTo must be provided or else there won't be a response
+ */
+ public static final int ROUTER_REMOVE_SESSION = 0x14;
+ public static final int ROUTER_REMOVE_SESSION_RESPONSE = 0x15;
+ //Response arguments
+ public static final int ROUTER_REMOVE_SESSION_RESPONSE_SUCESS = 0x00;
+ public static final int ROUTER_REMOVE_SESSION_RESPONSE_FAILED_APP_NOT_FOUND = 0x01;
+ public static final int ROUTER_REMOVE_SESSION_RESPONSE_FAILED_APP_ID_NOT_INCL = 0x02;
+ public static final int ROUTER_REMOVE_SESSION_RESPONSE_FAILED_SESSION_NOT_FOUND = 0x03;
+ public static final int ROUTER_REMOVE_SESSION_RESPONSE_FAILED_SESSION_ID_NOT_INCL = 0x04;
+ /**
+ * Command to have router service to send a packet
+ */
+ public static final int ROUTER_SEND_PACKET = 0x20;
+
+ //response
+ /**
+ * Router has received a packet and sent it to the client
+ */
+ public static final int ROUTER_RECEIVED_PACKET = 0x26;
+ //response
+
+ /**
+ * Command to tell router service details of secondary transport
+ */
+ public static final int ROUTER_REQUEST_SECONDARY_TRANSPORT_CONNECTION = 0x30;
+
+ //BUNDLE EXTRAS
+
+ public static final String FORMED_PACKET_EXTRA_NAME = "packet";
+
+ public static final String BYTES_TO_SEND_EXTRA_NAME = "bytes";
+ public static final String BYTES_TO_SEND_EXTRA_OFFSET = "offset";
+ public static final String BYTES_TO_SEND_EXTRA_COUNT = "count";
+ public static final String BYTES_TO_SEND_FLAGS = "flags";
+
+ public static final String PACKET_PRIORITY_COEFFICIENT = "priority_coefficient";
+
+ public static final String TRANSPORT_TYPE = "transport_type";
+ public static final String TRANSPORT_ADDRESS = "transport_address";
+
+ public static final int BYTES_TO_SEND_FLAG_NONE = 0x00;
+ public static final int BYTES_TO_SEND_FLAG_SDL_PACKET_INCLUDED = 0x01;
+ public static final int BYTES_TO_SEND_FLAG_LARGE_PACKET_START = 0x02;
+ public static final int BYTES_TO_SEND_FLAG_LARGE_PACKET_CONT = 0x04;
+ public static final int BYTES_TO_SEND_FLAG_LARGE_PACKET_END = 0x08;
+
+ public static final String CONNECTED_DEVICE_STRING_EXTRA_NAME = "devicestring";
+
+ public static final int PACKET_SENDING_ERROR_NOT_REGISTERED_APP = 0x00;
+ public static final int PACKET_SENDING_ERROR_NOT_CONNECTED = 0x01;
+ public static final int PACKET_SENDING_ERROR_UKNOWN = 0xFF;
+
+ public static final String ROUTER_SERVICE_VERSION = "router_service_version";
+
+ /**
+ * Status binder
+ */
+
+ public static final int ROUTER_STATUS_CONNECTED_STATE_REQUEST = 0x01;
+ public static final int ROUTER_STATUS_CONNECTED_STATE_RESPONSE = 0x02;
+ /**
+ * This flag when used to check router status will trigger the router service in sending out a ping that if it is connected to a device
+ */
+ public static final int ROUTER_STATUS_FLAG_TRIGGER_PING = 0x02;
+
+
+ /**
+ * Usb Transfer binder
+ */
+
+ public static final int USB_CONNECTED_WITH_DEVICE = 0x55;
+ public static final int ROUTER_USB_ACC_RECEIVED = 0x56;
+
+
+ /**
+ * Multiple-transports related constants
+ *
+ */
+ public static final String IAP_BLUETOOTH = "IAP_BLUETOOTH";
+ public static final String IAP_USB = "IAP_USB";
+ public static final String IAP_USB_HOST_MODE = "TCP_WIFI";
+ public static final String IAP_CARPLAY = "IAP_CARPLAY";
+ public static final String SPP_BLUETOOTH = "SPP_BLUETOOTH";
+ public static final String AOA_USB = "AOA_USB";
+ public static final String TCP_WIFI = "TCP_WIFI";
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/TransportManager.java b/base/src/main/java/com/smartdevicelink/transport/TransportManager.java
new file mode 100644
index 000000000..b816655cd
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/TransportManager.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (c) 2018 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.transport;
+
+
+import android.util.Log;
+
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.TransportRecord;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@SuppressWarnings("unused")
+public class TransportManager {
+ private static final String TAG = "TransportManager";
+
+ private final Object TRANSPORT_STATUS_LOCK;
+
+ WebSocketServer2 transport;
+ final List<TransportRecord> transportStatus;
+ final TransportEventListener transportListener;
+
+
+
+ /**
+ * Managing transports
+ * List for status of all transports
+ * If transport is not connected. Request Router service connect to it. Get connected message
+ */
+
+ public TransportManager(WebSocketServerConfig config, TransportEventListener listener){
+
+ this.transportListener = listener;
+ this.TRANSPORT_STATUS_LOCK = new Object();
+ synchronized (TRANSPORT_STATUS_LOCK){
+ this.transportStatus = new ArrayList<>();
+ }
+ final TransportRecord record = new TransportRecord(TransportType.WEB_SOCKET_SERVER,"127.0.0.1:"+config.port);
+ final List<TransportRecord> finalList = Collections.singletonList(record);
+ //Start the new transport
+ transport = new WebSocketServer2(config, new WebSocketServer2.Callback() {
+
+ @Override
+ public void onConnectionEstablished() {
+ synchronized (TRANSPORT_STATUS_LOCK){
+ transportStatus.clear();
+ transportStatus.addAll(finalList);
+ }
+ transportListener.onTransportConnected(finalList);
+ }
+
+ @Override
+ public void onError() {
+ Log.e(TAG, "Error in the transport manager from the web socket server");
+ if(transportListener != null){
+ transportListener.onError("");
+ }
+ }
+
+ @Override
+ public void onConnectionTerminated() {
+ if(record != null){
+ Log.d(TAG, "Transport disconnected - " + record);
+ }else{
+ Log.d(TAG, "Transport disconnected");
+
+ }
+
+ synchronized (TRANSPORT_STATUS_LOCK){
+ TransportManager.this.transportStatus.remove(record);
+ //Might check connectedTransports vs transportStatus to ensure they are equal
+ }
+ //Inform the transport listener that a transport has disconnected
+ transportListener.onTransportDisconnected("", record, new ArrayList<>()); //FIXME
+ }
+
+ @Override
+ public void onStateChanged(int previousState, int newState) {
+
+ }
+
+ @Override
+ public void onPacketReceived(SdlPacket packet) {
+ if(packet!=null){
+ transportListener.onPacketReceived(packet);
+ }
+ }
+ });
+
+ }
+
+ public void start(){
+ if(transport != null){
+ transport.start();
+ }else{
+ System.out.print("Unable to start transport.");
+ }
+ }
+
+ public void close(long sessionId){
+ if(transport != null) {
+ transport.stop();
+ }
+ }
+
+ /**
+ * Check to see if a transport is connected.
+ * @param transportType the transport to have its connection status returned. If `null` is
+ * passed in, all transports will be checked and if any are connected a
+ * true value will be returned.
+ * @param address the address associated with the transport type. If null, the first transport
+ * of supplied type will be used to return if connected.
+ * @return if a transport is connected based on included variables
+ */
+ public boolean isConnected(TransportType transportType, String address){
+ synchronized (TRANSPORT_STATUS_LOCK) {
+ if (transportType == null) {
+ return !transportStatus.isEmpty();
+ }
+ for (TransportRecord record : transportStatus) {
+ if (record.getType().equals(transportType)) {
+ if (address != null) {
+ if (address.equals(record.getAddress())) {
+ return true;
+ } // Address doesn't match, move forward
+ } else {
+ //If no address is included, assume any transport of correct type is acceptable
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ }
+ /**
+ * Retrieve a transport record with the supplied params
+ * @param transportType the transport to have its connection status returned.
+ * @param address the address associated with the transport type. If null, the first transport
+ * of supplied type will be returned.
+ * @return the transport record for the transport type and address if supplied
+ */
+ public TransportRecord getTransportRecord(TransportType transportType, String address){
+ synchronized (TRANSPORT_STATUS_LOCK) {
+ if (transportType == null) {
+ return null;
+ }
+ for (TransportRecord record : transportStatus) {
+ if (record.getType().equals(transportType)) {
+ if (address != null) {
+ if (address.equals(record.getAddress())) {
+ return record;
+ } // Address doesn't match, move forward
+ } else {
+ //If no address is included, assume any transport of correct type is acceptable
+ return record;
+ }
+ }
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Retrieves the currently connected transports
+ * @return the currently connected transports
+ */
+ public List<TransportRecord> getConnectedTransports(){
+ return this.transportStatus;
+ }
+
+ public boolean isHighBandwidthAvailable(){
+ synchronized (TRANSPORT_STATUS_LOCK) {
+ for (TransportRecord record : transportStatus) {
+ if (record.getType().equals(TransportType.USB)
+ || record.getType().equals(TransportType.TCP)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ //FIXME
+ public Object getRouterService(){
+ return null;
+ }
+
+ public void sendPacket(SdlPacket packet){
+ if(transport !=null){
+ transport.write(packet);
+ }else {
+
+ }
+ }
+
+ public void requestNewSession(TransportRecord transportRecord){
+ //FIXME do nothing
+ }
+
+ public void requestSecondaryTransportConnection(byte sessionId, Object params){
+ //FIXME do nothing
+ }
+
+ private synchronized void enterLegacyMode(final String info){
+ //FIXME do nothing
+ }
+
+ protected synchronized void exitLegacyMode(String info ){
+ //FIXME do nothing
+ }
+
+ public interface TransportEventListener{
+ /** Called to indicate and deliver a packet received from transport */
+ void onPacketReceived(SdlPacket packet);
+
+ /** Called to indicate that transport connection was established */
+ void onTransportConnected(List<TransportRecord> transports);
+
+ /** Called to indicate that transport was disconnected (by either side) */
+ void onTransportDisconnected(String info, TransportRecord type, List<TransportRecord> connectedTransports);
+
+ // Called when the transport manager experiences an unrecoverable failure
+ void onError(String info);
+ /**
+ * Called when the transport manager has determined it needs to move towards a legacy style
+ * transport connection. It will always be bluetooth.
+ * @param info simple info string about the situation
+ * @return if the listener is ok with entering legacy mode
+ */
+ boolean onLegacyModeEnabled(String info);
+ }
+
+
+
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/WebSocketServer2.java b/base/src/main/java/com/smartdevicelink/transport/WebSocketServer2.java
new file mode 100644
index 000000000..8355bef1b
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/WebSocketServer2.java
@@ -0,0 +1,157 @@
+package com.smartdevicelink.transport;
+
+import android.util.Log;
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.TransportRecord;
+import com.smartdevicelink.util.DebugTool;
+import org.java_websocket.WebSocket;
+import org.java_websocket.handshake.ClientHandshake;
+import org.java_websocket.server.WebSocketServer;
+
+import java.net.InetSocketAddress;
+import java.nio.ByteBuffer;
+
+public class WebSocketServer2 extends WebSocketServer {
+ private static final String TAG = "WebSocketServer2";
+ com.smartdevicelink.transport.WebSocketServer2.Callback callback;
+ WebSocketServerConfig config;
+ WebSocket webSocket;
+ SdlPsm psm;
+
+ final TransportRecord record;
+
+ public WebSocketServer2(WebSocketServerConfig config, com.smartdevicelink.transport.WebSocketServer2.Callback callback){
+ super((new InetSocketAddress(config.port)));
+
+ this.config = config;
+ this.callback = callback;
+ record = new TransportRecord(TransportType.WEB_SOCKET_SERVER,"127.0.0.1:" + config.port); //If changed, change in transport manager as well
+ //This will set the connection lost timeout to not occur. So we might ping, but not pong
+ this.setConnectionLostTimeout(config.connectionLostTimeout);
+
+ }
+
+ public void stop(){
+ try {
+ this.stop(500);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void write(SdlPacket packet){
+ //Log.i(TAG, "Atttempt to write packet " + packet);
+ if(packet != null
+ && this.webSocket != null
+ && this.webSocket.isOpen()) {
+ this.webSocket.send(packet.constructPacket());
+ }
+
+ }
+
+ @Override
+ public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {
+ Log.i(TAG, "onOpen");
+ this.webSocket = webSocket;
+
+ if(callback!=null){
+ callback.onConnectionEstablished();
+ }
+ }
+
+ @Override
+ public void onClose(WebSocket webSocket, int i, String s, boolean b) {
+ Log.i(TAG, "onClose");
+ try{
+ DebugTool.logInfo("Closing id - " + i);
+ DebugTool.logInfo("Closing string - " + s);
+ DebugTool.logInfo("Closing from remote? " + b);
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+
+ if(callback!=null) {
+ callback.onConnectionTerminated();
+ }
+ }
+
+
+ @Override
+ public void onWebsocketCloseInitiated(WebSocket conn, int code, String reason) {
+ super.onWebsocketCloseInitiated(conn, code, reason);
+ try{
+ DebugTool.logInfo("Code - " + code + " Reason - " + reason);
+ }catch (Exception e){}
+ }
+
+ @Override
+ public void onMessage(WebSocket webSocket, String s) {
+ Log.i(TAG, "on Message - String");
+
+ //TODO
+
+ }
+
+ @Override
+ public void onMessage(WebSocket conn, ByteBuffer message) {
+ super.onMessage(conn, message);
+ //Log.i(TAG, "on Message - ByteBuffer");
+ byte input;
+
+ if(message != null){
+ synchronized (WebSocketServer2.this) {
+ boolean stateProgress;
+ while (message.hasRemaining()) {
+ input = message.get();
+ stateProgress = psm.handleByte(input);
+ if (!stateProgress) {//We are trying to weed through the bad packet info until we get something
+
+ //Log.w(TAG, "Packet State Machine did not move forward from state - "+ psm.getState()+". PSM being Reset.");
+ psm.reset();
+ }
+
+ if (psm.getState() == SdlPsm.FINISHED_STATE) {
+ synchronized (com.smartdevicelink.transport.WebSocketServer2.this) {
+ SdlPacket packet = psm.getFormedPacket();
+ if (callback != null && packet != null) {
+ /// Log.i(TAG, "Read a packet: " + packet);
+ packet.setTransportRecord(record);
+ callback.onPacketReceived(packet);
+ }
+ }
+ //We put a trace statement in the message read so we can avoid all the extra bytes
+ psm.reset();
+ }
+ }
+ }
+
+ }
+
+ }
+
+
+
+ @Override
+ public void onError(WebSocket webSocket, Exception e) {
+ Log.e(TAG, "bad", e);
+ if(callback!=null) {
+ callback.onError();
+ }
+ }
+
+ @Override
+ public void onStart() {
+ Log.i(TAG, "onStart");
+ psm = new SdlPsm();
+
+ }
+
+ public interface Callback{
+ void onConnectionEstablished();
+ void onError();
+ void onConnectionTerminated();
+ void onStateChanged(int previousState, int newState); //TODO determine if this is needed
+ void onPacketReceived(SdlPacket packet);
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/WebSocketServerConfig.java b/base/src/main/java/com/smartdevicelink/transport/WebSocketServerConfig.java
new file mode 100644
index 000000000..19a957419
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/WebSocketServerConfig.java
@@ -0,0 +1,25 @@
+package com.smartdevicelink.transport;
+
+import com.smartdevicelink.transport.enums.TransportType;
+
+public class WebSocketServerConfig extends BaseTransportConfig{
+
+ final int port, connectionLostTimeout;
+
+ /**
+ * Default constructor for WebsocketConfig
+ * @param port the port this web socket should listen on
+ * @param connectionLostTimeout the timeout for a connection lost, default would be 60 seconds. If a value less than
+ * 0 is used, then the websocket will wait indefinitely.
+ */
+ public WebSocketServerConfig(int port, int connectionLostTimeout){
+ this.port = port;
+ this.shareConnection = false;
+ this.connectionLostTimeout = connectionLostTimeout;
+ }
+
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.WEB_SOCKET_SERVER;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/enums/TransportType.java b/base/src/main/java/com/smartdevicelink/transport/enums/TransportType.java
new file mode 100644
index 000000000..5c2dbb23d
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/enums/TransportType.java
@@ -0,0 +1,33 @@
+package com.smartdevicelink.transport.enums;
+
+/**
+ * Defines available types of the transports.
+ */
+public enum TransportType {
+ /**
+ * Transport type will be anything the multiplexing service connects to.
+ */
+ MULTIPLEX,
+ /**
+ * Transport type is Bluetooth.
+ */
+ BLUETOOTH,
+
+ /**
+ * Transport type is TCP.
+ */
+ TCP,
+ USB,
+ /**
+ * Used for cloud and embedded
+ */
+ WEB_SOCKET_SERVER;
+
+ public static TransportType valueForString(String value) {
+ try{
+ return valueOf(value);
+ }catch(Exception e){
+ return null;
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java b/base/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java
new file mode 100644
index 000000000..bcfc2e373
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2018 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.smartdevicelink.transport.utl;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.smartdevicelink.transport.enums.TransportType;
+
+public class TransportRecord implements Parcelable{
+
+ private TransportType type;
+ private String address;
+
+ public TransportRecord(TransportType transportType, String address){
+ this.type = transportType;
+ this.address = address;
+ }
+
+ public TransportType getType() {
+ return type;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if(obj == null) {
+ return false;
+ }
+
+ if (obj instanceof TransportRecord) {
+ TransportRecord record = (TransportRecord) obj;
+ return record.type != null && record.type.equals(type) //Transport type is the same
+ && ((record.address == null && address == null) //Both addresses are null
+ || (record.address != null && record.address.equals(address))); //Or they match
+ }
+
+ return super.equals(obj);
+ }
+
+ @Override
+ public String toString(){
+ StringBuilder builder = new StringBuilder();
+ builder.append("Transport Type: ");
+ builder.append(type.name());
+ builder.append(" Address: ");
+ builder.append(address);
+ return builder.toString();
+ }
+
+ public TransportRecord(Parcel p){
+ if (p.readInt() == 1) { //We should have a transport type attached
+ String transportName = p.readString();
+ if(transportName != null){
+ this.type = TransportType.valueOf(transportName);
+ }
+ }
+
+ if (p.readInt() == 1) { //We should have a transport address attached
+ address = p.readString();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type!=null? 1 : 0);
+ if(type != null){
+ dest.writeString(type.name());
+ }
+
+ dest.writeInt(address !=null? 1 : 0);
+ if(address != null){
+ dest.writeString(address);
+ }
+ }
+
+ public static final Parcelable.Creator<TransportRecord> CREATOR = new Parcelable.Creator<TransportRecord>() {
+ public TransportRecord createFromParcel(Parcel in) {
+ return new TransportRecord(in);
+ }
+
+ @Override
+ public TransportRecord[] newArray(int size) {
+ return new TransportRecord[size];
+ }
+
+ };
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/BitConverter.java b/base/src/main/java/com/smartdevicelink/util/BitConverter.java
new file mode 100644
index 000000000..153418c8a
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/BitConverter.java
@@ -0,0 +1,116 @@
+package com.smartdevicelink.util;
+
+public class BitConverter {
+ /**
+ * @param bytes byte array that will be converted to hex
+ * @return the String containing converted hex values or null if byte array is null
+ */
+ public static String bytesToHex(byte [] bytes) {
+ if (bytes == null) { return null; }
+ return bytesToHex(bytes, 0, bytes.length);
+ } // end-method
+
+ /**
+ * @param bytes byte array that will be converted to hex
+ * @param offset int representing the offset to begin conversion at
+ * @param length int representing number of bytes in array to convert
+ * @return the String containing converted hex values or null if byte array is null
+ */
+ public static String bytesToHex(byte[] bytes, int offset, int length) {
+ if (bytes == null) { return null; }
+ final char[] HexDigits = new char[] {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
+ byte b;
+ char[] hexChars = new char[2 * length];
+ //StringBuffer sb = new StringBuffer();
+ int upperBound = Math.min(bytes.length, (offset + length));
+ int baidx = 0;
+ int sidx = 0;
+ for (baidx = offset; baidx < upperBound; baidx++) {
+ // Get the byte from the array
+ b = bytes[baidx];
+ // Use nibbles as index into hex digit array (left nibble, then right)
+ hexChars[sidx++] = HexDigits[(b & 0xf0) >> 4];
+ hexChars[sidx++] = HexDigits[(b & 0x0f)];
+ } // end-for
+ return new String(hexChars);
+ } // end-method
+
+ /**
+ * @param hexString the String containing converted hex values
+ * @return byte array converted from input String or null if String is null
+ */
+ public static byte [] hexToBytes(String hexString) {
+ if (hexString == null) { return null; }
+ if (hexString.length() % 2 != 0) {
+ hexString = "0" + hexString;
+ }
+ byte [] theBytes = new byte[hexString.length() / 2];
+ for (int i = 0; i < hexString.length(); i += 2) {
+ String byteString = hexString.substring(i, i + 2);
+ byte theByte = (byte)Integer.parseInt(byteString, 16);
+ theBytes[i/2] = theByte;
+ }
+ return theBytes;
+ } // end-method
+
+ public static final byte[] intToByteArray(int value) {
+ return new byte[] {
+ (byte)(value >>> 24),
+ (byte)(value >>> 16),
+ (byte)(value >>> 8),
+ (byte)value};
+ }
+
+ /**
+ * @param sizeBuf byte array that will be converted to int
+ * @return int converted from byte array or -1 if byte array is null
+ */
+ public static int intFromByteArray(byte[] sizeBuf, int offset) {
+ if (sizeBuf == null) { return -1; }
+ int ret = 0;
+ for (int i = offset; i < offset + 4; i++) {
+ ret <<= 8;
+ ret |= 0xFF & sizeBuf[i];
+ }
+ return ret;
+ }
+
+ public static final byte[] shortToByteArray(short value) {
+ return new byte[] {
+ (byte)(value >>> 8),
+ (byte)value};
+ }
+
+ /**
+ * @param sizeBuf byte array that will be converted to short
+ * @return short converted from byte array or -1 if byte array is null
+ */
+ public static short shortFromByteArray(byte[] sizeBuf, int offset) {
+ if (sizeBuf == null) { return -1; }
+ short ret = 0;
+ for (int i = offset; i < offset + 2; i++) {
+ ret <<= 8;
+ ret |= 0xFF & sizeBuf[i];
+ }
+ return ret;
+ }
+
+ /**
+ * Converts the byte array into a string of hex values.
+ * @param bytes byte array that will be converted to hex
+ * @param end EXCLUSIVE so if it it receives 10 it will print 0-9
+ * @return the String containing converted hex values or null if byte array is null
+ */
+ public static String bytesToHex(byte[] bytes,int end){
+ if (bytes == null) { return null; }
+ if(bytes.length<end){
+ end = bytes.length;
+ }
+ StringBuilder sb = new StringBuilder();
+ for(int i=0;i<end;i++){
+ sb.append(" ");
+ sb.append(String.format("%02X ", bytes[i]));
+ }
+ return sb.toString();
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/ByteEnumer.java b/base/src/main/java/com/smartdevicelink/util/ByteEnumer.java
new file mode 100644
index 000000000..8ed58420a
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/ByteEnumer.java
@@ -0,0 +1,60 @@
+package com.smartdevicelink.util;
+
+import java.util.Enumeration;
+import java.util.Vector;
+
+public abstract class ByteEnumer {
+
+ protected ByteEnumer(byte value, String name) {
+ this.value = value;
+ this.name = name;
+ }
+
+ private byte value;
+ private String name;
+
+ public byte getValue() { return value; }
+ public String getName() { return name; }
+
+ public boolean equals(ByteEnumer other) {
+ return name == other.getName();
+ }
+
+ public boolean eq(ByteEnumer other) {
+ return equals(other);
+ }
+
+ public byte value() {
+ return value;
+ }
+
+ public static ByteEnumer get(Vector<?> theList, byte value) {
+ Enumeration<?> enumer = theList.elements();
+ while (enumer.hasMoreElements()) {
+ try {
+ ByteEnumer current = (ByteEnumer)enumer.nextElement();
+ if (current.getValue() == value) {
+ return current;
+ }
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ public static ByteEnumer get(Vector<?> theList, String name) {
+ Enumeration<?> enumer = theList.elements();
+ while (enumer.hasMoreElements()) {
+ try {
+ ByteEnumer current = (ByteEnumer)enumer.nextElement();
+ if (current.getName().equals(name)) {
+ return current;
+ }
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+} \ No newline at end of file
diff --git a/base/src/main/java/com/smartdevicelink/util/CorrelationIdGenerator.java b/base/src/main/java/com/smartdevicelink/util/CorrelationIdGenerator.java
new file mode 100644
index 000000000..18ff93622
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/CorrelationIdGenerator.java
@@ -0,0 +1,25 @@
+package com.smartdevicelink.util;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class CorrelationIdGenerator {
+
+ private static final int CORRELATION_ID_START = 0;
+
+ private static final AtomicInteger sNextCorrelationId = new AtomicInteger(CORRELATION_ID_START);
+
+ public static int generateId() {
+ for (;;) {
+ final int result = sNextCorrelationId.get();
+ // aapt-generated IDs have the high byte nonzero; clamp to the range under that.
+ int newValue = result + 1;
+
+ if (newValue > 0x00FFFFFF){
+ newValue = CORRELATION_ID_START; // Roll over to 0.
+ }
+ if (sNextCorrelationId.compareAndSet(result, newValue)) {
+ return result;
+ }
+ }
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/DebugTool.java b/base/src/main/java/com/smartdevicelink/util/DebugTool.java
new file mode 100644
index 000000000..0d005070b
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/DebugTool.java
@@ -0,0 +1,354 @@
+package com.smartdevicelink.util;
+
+import android.util.Log;
+
+import com.smartdevicelink.exception.SdlException;
+import com.smartdevicelink.marshal.JsonRPCMarshaller;
+import com.smartdevicelink.protocol.BinaryFrameHeader;
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.SdlPacket;
+import com.smartdevicelink.protocol.enums.FunctionID;
+import com.smartdevicelink.protocol.enums.MessageType;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.RPCMessage;
+import com.smartdevicelink.proxy.RPCStruct;
+import com.smartdevicelink.transport.SiphonServer;
+
+import java.util.Hashtable;
+import java.util.Vector;
+
+public class DebugTool {
+
+
+ public static final String TAG = "SdlProxy";
+
+ private static boolean isErrorEnabled = false;
+ private static boolean isWarningEnabled = false;
+ private static boolean isInfoEnabled = false;
+
+ public static void enableDebugTool() {
+ isErrorEnabled = true;
+ isWarningEnabled = true;
+ isInfoEnabled = true;
+ }
+
+ public static void disableDebugTool() {
+ isErrorEnabled = true;
+ isWarningEnabled = false;
+ isInfoEnabled = false;
+ }
+
+ public static boolean isDebugEnabled()
+ {
+ if (isWarningEnabled && isInfoEnabled) return true;
+
+ return false;
+ }
+
+ private static String prependProxyVersionNumberToString(String string) {
+ /* FIXME if (Version.VERSION != null && string != null) {
+ string = Version.VERSION + ": " + string;
+ }*/
+
+ return string;
+ }
+
+ public static void logError(String msg) {
+
+ Boolean wasWritten = false;
+
+ msg = prependProxyVersionNumberToString(msg);
+
+ wasWritten = logToSiphon(msg);
+
+ if (isErrorEnabled && !wasWritten) {
+ NativeLogTool.logError(TAG, msg);
+ }
+ }
+
+ public static void logError(String msg, Throwable ex) {
+ Boolean wasWritten = false;
+
+ msg = prependProxyVersionNumberToString(msg);
+
+ if (ex != null) {
+ wasWritten = logToSiphon(msg + " Exception String: " + ex.toString());
+ } else {
+ wasWritten = logToSiphon(msg);
+ }
+
+ if (isErrorEnabled && !wasWritten) {
+ NativeLogTool.logError(TAG, msg, ex);
+ }
+ }
+
+ public static void logWarning(String msg) {
+ Boolean wasWritten = false;
+
+ msg = prependProxyVersionNumberToString(msg);
+
+ wasWritten = logToSiphon(msg);
+
+ if (isWarningEnabled && !wasWritten) {
+ NativeLogTool.logWarning(TAG, msg);
+ }
+ }
+
+ public static void logInfo(String msg) {
+ Boolean wasWritten = false;
+
+ msg = prependProxyVersionNumberToString(msg);
+
+ wasWritten = logToSiphon(msg);
+
+ if (isInfoEnabled && !wasWritten) {
+ NativeLogTool.logInfo(TAG, msg);
+ }
+ }
+
+ public static void logInfo(String msg, boolean bPrependVersion) {
+ Boolean wasWritten = false;
+
+ if (bPrependVersion) msg = prependProxyVersionNumberToString(msg);
+
+ wasWritten = logToSiphon(msg);
+
+ if (isInfoEnabled && !wasWritten) {
+ NativeLogTool.logInfo(TAG, msg);
+ }
+ }
+
+ protected static Boolean logToSiphon(String msg) {
+ if (SiphonServer.getSiphonEnabledStatus()) {
+ // Initialize the SiphonServer, will be ignored if already initialized
+ SiphonServer.init();
+
+ // Write to the SiphonServer
+ return SiphonServer.sendSiphonLogData(msg);
+ }
+ return false;
+ }
+
+ protected static String getLine(Throwable ex) {
+ if (ex == null) { return null; }
+ String toPrint = ex.toString() + " :" + ex.getMessage();
+ for (int i=0; i<ex.getStackTrace().length; i++) {
+ StackTraceElement elem = ex.getStackTrace()[i];
+ toPrint += "\n " + elem.toString();
+ }
+
+ if (ex instanceof SdlException) {
+ SdlException sdlEx = (SdlException) ex;
+ if (sdlEx.getInnerException() != null && sdlEx != sdlEx.getInnerException()) {
+ toPrint += "\n nested:\n";
+ toPrint += getLine(sdlEx.getInnerException());
+ }
+ }
+
+ return toPrint;
+ }
+
+
+ protected static Vector<IConsole> consoleListenerList = new Vector<IConsole>();
+
+ protected final static boolean isTransportEnabled = false;
+ protected final static boolean isRPCEnabled = false;
+
+ public static void addConsole(IConsole console) {
+ synchronized(consoleListenerList) {
+ consoleListenerList.addElement(console);
+ }
+ }
+
+ public static void removeConsole(IConsole console) {
+ synchronized(consoleListenerList) {
+ consoleListenerList.removeElement(console);
+ }
+ }
+
+ public static void clearConsoles() {
+ synchronized(consoleListenerList) {
+ consoleListenerList.removeAllElements();
+ }
+ }
+
+ public static void logTransport(String msg) {
+ if (isTransportEnabled) {
+ Log.d(TAG, msg);
+ logInfoToConsole(msg);
+ }
+ }
+
+ public static void logRPCSend(String rpcMsg) {
+ if (isRPCEnabled) {
+ Log.d(TAG, "Sending RPC message: " + rpcMsg);
+ logRPCSendToConsole(rpcMsg);
+ }
+ }
+
+ public static void logRPCReceive(String rpcMsg) {
+ if (isRPCEnabled) {
+ Log.d(TAG, "Received RPC message: " + rpcMsg);
+ logRPCSendToConsole(rpcMsg);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static void logInfoToConsole(String msg) {
+ Vector<IConsole> localList;
+ synchronized(consoleListenerList) {
+ localList = (Vector<IConsole>) consoleListenerList.clone();
+ }
+
+ for (int i = 0; i < localList.size(); i++) {
+ IConsole consoleListener = (IConsole) localList.elementAt(i);
+ try {
+ consoleListener.logInfo(msg);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failure propagating logInfo: " + ex.toString(), ex);
+ } // end-catch
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static void logErrorToConsole(String msg) {
+ Vector<IConsole> localList;
+ synchronized(consoleListenerList) {
+ localList = (Vector<IConsole>) consoleListenerList.clone();
+ }
+ for (int i = 0; i < localList.size(); i++) {
+ IConsole consoleListener = (IConsole) localList.elementAt(i);
+ try {
+ consoleListener.logError(msg);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failure propagating logError: " + ex.toString(), ex);
+ } // end-catch
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static void logErrorToConsole(String msg, Throwable e) {
+ Vector<IConsole> localList;
+ synchronized(consoleListenerList) {
+ localList = (Vector<IConsole>) consoleListenerList.clone();
+ }
+
+ for (int i = 0; i < localList.size(); i++) {
+ IConsole consoleListener = (IConsole) localList.elementAt(i);
+ try {
+ consoleListener.logError(msg, e);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failure propagating logError: " + ex.toString(), ex);
+ } // end-catch
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static void logRPCSendToConsole(String msg) {
+ Vector<IConsole> localList;
+ synchronized(consoleListenerList) {
+ localList = (Vector<IConsole>) consoleListenerList.clone();
+ }
+
+ for (int i = 0; i < localList.size(); i++) {
+ IConsole consoleListener = (IConsole) localList.elementAt(i);
+ try {
+ consoleListener.logRPCSend(msg);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failure propagating logRPCSend: " + ex.toString(), ex);
+ } // end-catch
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ protected static void logRPCReceiveToConsole(String msg) {
+ Vector<IConsole> localList;
+ synchronized(consoleListenerList) {
+ localList = (Vector<IConsole>) consoleListenerList.clone();
+ }
+
+ for (int i = 0; i < localList.size(); i++) {
+ IConsole consoleListener = (IConsole) localList.elementAt(i);
+ try {
+ consoleListener.logRPCReceive(msg);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failure propagating logRPCReceive: " + ex.toString(), ex);
+ } // end-catch
+ }
+ }
+
+ /**
+ * Debug method to try to extract the RPC hash from the packet payload. Should only be used while debugging, not in production.
+ * Currently it will only handle single frame RPCs
+ * @param packet to inspect
+ * @return The Hashtable to be used to construct an RPC
+ */
+ public static Hashtable<String, Object> getRPCHash(SdlPacket packet){
+ if(packet == null ||
+ packet.getFrameType().getValue() != SdlPacket.FRAME_TYPE_SINGLE ||
+ packet.getServiceType()!=SdlPacket.SERVICE_TYPE_RPC){
+ Log.w("Debug", "Unable to get hash");
+ return null;
+ }
+ int version = packet.getVersion();
+
+ ProtocolMessage message = new ProtocolMessage();
+ SessionType serviceType = SessionType.valueOf((byte)packet.getServiceType());
+ if (serviceType == SessionType.RPC) {
+ message.setMessageType(MessageType.RPC);
+ } else if (serviceType == SessionType.BULK_DATA) {
+ message.setMessageType(MessageType.BULK);
+ } // end-if
+ message.setSessionType(serviceType);
+ message.setSessionID((byte)packet.getSessionId());
+ //If it is WiPro 2.0 it must have binary header
+ if (version > 1) {
+ BinaryFrameHeader binFrameHeader = BinaryFrameHeader.
+ parseBinaryHeader(packet.getPayload());
+ if(binFrameHeader == null) {
+ return null;
+ }
+ message.setVersion((byte) version);
+ message.setRPCType(binFrameHeader.getRPCType());
+ message.setFunctionID(binFrameHeader.getFunctionID());
+ message.setCorrID(binFrameHeader.getCorrID());
+ if (binFrameHeader.getJsonSize() > 0){
+ message.setData(binFrameHeader.getJsonData());
+ }
+ if (binFrameHeader.getBulkData() != null){
+ message.setBulkData(binFrameHeader.getBulkData());
+ }
+ } else {
+ message.setData(packet.getPayload());
+ }
+ Hashtable<String, Object> hash = new Hashtable<String, Object>();
+ if (packet.getVersion() > 1) {
+ Hashtable<String, Object> hashTemp = new Hashtable<String, Object>();
+
+ hashTemp.put(RPCMessage.KEY_CORRELATION_ID, message.getCorrID());
+ if (message.getJsonSize() > 0) {
+ final Hashtable<String, Object> mhash = JsonRPCMarshaller.unmarshall(message.getData());
+ hashTemp.put(RPCMessage.KEY_PARAMETERS, mhash);
+ }
+
+ String functionName = FunctionID.getFunctionName(message.getFunctionID());
+ if (functionName != null) {
+ hashTemp.put(RPCMessage.KEY_FUNCTION_NAME, functionName);
+ } else {
+ return null;
+ }
+ if (message.getRPCType() == 0x00) {
+ hash.put(RPCMessage.KEY_REQUEST, hashTemp);
+ } else if (message.getRPCType() == 0x01) {
+ hash.put(RPCMessage.KEY_RESPONSE, hashTemp);
+ } else if (message.getRPCType() == 0x02) {
+ hash.put(RPCMessage.KEY_NOTIFICATION, hashTemp);
+ }
+ if (message.getBulkData() != null) hash.put(RPCStruct.KEY_BULK_DATA, message.getBulkData());
+ } else {
+ final Hashtable<String, Object> mhash = JsonRPCMarshaller.unmarshall(message.getData());
+ hash = mhash;
+ }
+ return hash;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/FileUtls.java b/base/src/main/java/com/smartdevicelink/util/FileUtls.java
new file mode 100644
index 000000000..45ac19870
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/FileUtls.java
@@ -0,0 +1,41 @@
+package com.smartdevicelink.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.LinkOption;
+import java.nio.file.Path;
+
+public class FileUtls {
+
+
+ /**
+ *
+ * @param file should include path and name
+ * @return
+ */
+ public static boolean doesFileExist(String file){
+ try{
+ return false;//Files.exists(new Path(file), (LinkOption)null);
+ }catch (Exception e){
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ public static byte[] getFileData(String path){
+ byte[] data = null;
+ if(path != null && path.length() > 0) {
+ if (doesFileExist(path)) {
+ try {
+ return Files.readAllBytes(new File("/path/to/file").toPath());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/HttpRequestTask.java b/base/src/main/java/com/smartdevicelink/util/HttpRequestTask.java
new file mode 100644
index 000000000..be7542000
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/HttpRequestTask.java
@@ -0,0 +1,180 @@
+package com.smartdevicelink.util;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+import android.os.AsyncTask;
+import android.util.Log;
+
+public class HttpRequestTask extends AsyncTask<String, String, String> {
+ private static final String TAG = "Http Request Task";
+
+ public static final String REQUEST_TYPE_POST = "POST";
+ public static final String REQUEST_TYPE_GET = "GET";
+ public static final String REQUEST_TYPE_DELETE = "DELETE";
+
+ HttpRequestTaskCallback cb;
+
+ /**
+ * @param hcb callback for when this task finishes
+ * <br><br><b> - When calling execute, params as followed: </b><br>
+ * 1. Url String<br>
+ * 2. Request type (Defined in this class) REQUEST_TYPE_POST, REQUEST_TYPE_GET, REQUEST_TYPE_DELETE<br>
+ * 3. (Optional) Data to be sent. <br>
+ * 4. (Optional) Content Type Default will be application/json<br>
+ * 5. (Optional) Accept Type default will be application/json
+ *
+ */
+ public HttpRequestTask( HttpRequestTaskCallback hcb){
+ this.cb = hcb;
+ }
+
+ @Override
+ protected String doInBackground(String... params) {
+ int length = params.length;
+ String urlString = params[0];
+ String request_type = params[1];
+
+ //Grab and set data to be written if included
+ String data;
+ if(length>2){
+ data = params[2];
+ }else{
+ data = null;
+ }
+
+ //Grab and set content type for the header if included
+ String contentType;
+ if(length>3){
+ contentType = params[3];
+ }else{
+ contentType = "application/json";
+ }
+ //Grab and set accept type for the header if included
+ String acceptType;
+ if(length>4){
+ acceptType = params[4];
+ }else{
+ acceptType = "application/json";
+ }
+
+ if(urlString == null || request_type == null){
+ Log.e(TAG, "Can't process request, param error");
+ if(cb!=null){
+ cb.httpFailure(-1);
+ cb = null;
+ }
+ return "Error";
+ }
+
+ HttpURLConnection urlConnection = null;
+ BufferedReader reader = null;
+ try {
+ URL url = new URL(urlString);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setDoOutput(true);
+ urlConnection.setRequestMethod(request_type);
+ urlConnection.setRequestProperty("Content-Type", contentType);
+ urlConnection.setRequestProperty("Accept", acceptType);
+ //If we have data, we should write it out
+ if(data !=null){
+ Writer writer = new BufferedWriter(new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8"));
+ writer.write(data);
+ writer.close();
+ }
+ InputStream inputStream = urlConnection.getInputStream();
+
+ int responseCode = urlConnection.getResponseCode();
+ if (responseCode == 200) { //Success
+ //input stream
+ StringBuffer buffer = new StringBuffer();
+ if (inputStream == null) {
+ // Nothing to do.
+ if(cb!=null){
+ cb.httpCallComplete(null);
+ cb = null;
+ }
+ return null;
+ }
+ reader = new BufferedReader(new InputStreamReader(inputStream));
+
+ String inputLine;
+ while ((inputLine = reader.readLine()) != null)
+ buffer.append(inputLine + "\n");
+ if (buffer.length() == 0) {
+ // Stream was empty. No point in parsing.
+ if(cb!=null){
+ cb.httpCallComplete(null);
+ cb = null;
+ }
+ return null;
+ }
+ String response = null;
+
+ response = buffer.toString();
+ //send to post execute
+ if(cb!=null){
+ cb.httpCallComplete(response);
+ cb = null;
+ }
+ return response;
+ }else{
+ if(cb!=null){
+ cb.httpFailure(responseCode);
+ cb = null;
+ }
+ Log.e(TAG, "Failed to download file - " + responseCode);
+ return null;
+ }
+
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e){ // Only to catch error in urlConnection.getOutputStream() - when servers are down
+ e.printStackTrace();
+ urlConnection = null;
+ }
+ finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (final IOException e) {
+ Log.e(TAG, "Error closing stream", e);
+ }
+ }
+ if(cb!=null){
+ cb.httpFailure(-1);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Callback interface for HTTP requests.
+ * @author Joey Grover
+ *
+ */
+ public interface HttpRequestTaskCallback{
+ /**
+ * Called when HTTP request is successfully completed.
+ * @param response The response to the HTTP request.
+ */
+ public abstract void httpCallComplete(String response);
+ /**
+ * Called when HTTP request failed.
+ * @param statusCode The HTTP failure code.
+ */
+ public abstract void httpFailure(int statusCode);
+ }
+
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/IConsole.java b/base/src/main/java/com/smartdevicelink/util/IConsole.java
new file mode 100644
index 000000000..b388f77f9
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/IConsole.java
@@ -0,0 +1,10 @@
+package com.smartdevicelink.util;
+
+
+public interface IConsole {
+ void logInfo(String msg);
+ void logError(String msg);
+ void logError(String msg, Throwable ex);
+ void logRPCSend(String rpcMsg);
+ void logRPCReceive(String rpcMsg);
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/NativeLogTool.java b/base/src/main/java/com/smartdevicelink/util/NativeLogTool.java
new file mode 100644
index 000000000..488ddac29
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/NativeLogTool.java
@@ -0,0 +1,112 @@
+package com.smartdevicelink.util;
+
+import android.util.Log;
+
+public class NativeLogTool {
+ private static String TAG = "NativeLogTool";
+ static private boolean logToSystemEnabled = true;
+ private static final int ChunkSize = 4000;
+
+ public enum LogTarget {
+ Info
+ ,Warning
+ ,Error;
+
+ public static LogTarget valueForString (String value) {
+ try{
+ return valueOf(value);
+ } catch(Exception e) {
+ return null;
+ }
+ }
+ }
+
+ public static void setEnableState(boolean en) {
+ logToSystemEnabled = en;
+ } // end-method
+
+ public static boolean isEnabled() {
+ return logToSystemEnabled;
+ } // end-method
+
+ public static boolean logInfo(String message) {
+ return logInfo(TAG, message);
+ }
+
+ public static boolean logInfo(String tag, String message) {
+ if (logToSystemEnabled) {
+ return log(LogTarget.Info, tag, message);
+ }
+ return false;
+ }
+
+ public static boolean logWarning(String message) {
+ return logWarning(TAG, message);
+ }
+
+ public static boolean logWarning(String tag, String message) {
+ if (logToSystemEnabled) {
+ return log(LogTarget.Warning, tag, message);
+ }
+ return false;
+ }
+
+ public static boolean logError(String message) {
+ return logError(TAG, message);
+ }
+
+ public static boolean logError(String tag, String message) {
+ if (logToSystemEnabled) {
+ return log(LogTarget.Error, tag, message);
+ }
+ return false;
+ }
+
+ public static boolean logError(String message, Throwable t) {
+ return logError(TAG, message, t);
+ }
+
+ public static boolean logError(String tag, String message, Throwable t) {
+ // If the call to logError is passed a throwable, write directly to the system log
+ if (logToSystemEnabled) {
+ Log.e(tag, message, t);
+ }
+ return logToSystemEnabled;
+ }
+
+ private static boolean log(LogTarget ltarg, String source, String logMsg) {
+ // Don't log empty messages
+ if (logMsg == null || logMsg.length() == 0) {
+ return false;
+ }
+
+ int bytesWritten = 0;
+ int substrSize = 0;
+ String tag = source;
+ String chunk = null;
+ try {
+ for (int idx=0;idx < logMsg.length();idx += substrSize) {
+ substrSize = Math.min(ChunkSize, logMsg.length() - idx);
+ chunk = logMsg.substring(idx, idx + substrSize);
+ switch (ltarg) {
+ case Info:
+ bytesWritten = Log.i(tag, chunk);
+ break;
+ case Warning:
+ bytesWritten = Log.w(tag, chunk);
+ break;
+ case Error:
+ bytesWritten = Log.e(tag, chunk);
+ break;
+ }
+ if (bytesWritten < chunk.length()) {
+ //Log.e(TAG, "Calling Log.e: msg length=" + chunk.length() + ", bytesWritten=" + bytesWritten);
+ }
+ }
+ } catch (Exception ex) {
+ Log.e(TAG, "Failure writing " + ltarg.name() + " fragments to android log:" + ex.toString());
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/SdlDataTypeConverter.java b/base/src/main/java/com/smartdevicelink/util/SdlDataTypeConverter.java
new file mode 100644
index 000000000..0b47a59cd
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/SdlDataTypeConverter.java
@@ -0,0 +1,63 @@
+package com.smartdevicelink.util;
+
+/**
+ * This is a utility class to aid in handling values stored in the RPC classes.
+ */
+public class SdlDataTypeConverter {
+
+ /**
+ * Converts values that are retrieved from an RPC parameters Hashtable as an
+ * Object into the standard number value of the mobile API, Double.
+ *
+ * @param originalValue The value retrieved from an RPC parameters Hashtable.
+ * @return The Double representation of an integer or double value stored in
+ * the Object, or null if the value could not be converted.
+ */
+ public static Double objectToDouble(Object originalValue) {
+
+ if (originalValue == null) {
+ return null;
+ }
+
+ Double result = null;
+
+ // Uses reflection to determine if the object is a valid type.
+ if (originalValue instanceof Integer) {
+ result = ((Integer) originalValue).doubleValue();
+ } else if (originalValue instanceof Float){
+ result = ((Float) originalValue).doubleValue();
+ } else if (originalValue instanceof Double){
+ result = (Double) originalValue;
+ }
+
+ return result;
+ }
+
+ /**
+ * Converts values that are retrieved from an RPC parameters Hashtable as an
+ * Object into the standard number value of the mobile API, Float.
+ *
+ * @param originalValue The value retrieved from an RPC parameters Hashtable.
+ * @return The Float representation of an integer or float value stored in
+ * the Object, or null if the value could not be converted.
+ */
+ public static Float objectToFloat(Object originalValue) {
+
+ if (originalValue == null) {
+ return null;
+ }
+
+ Float result = null;
+
+ // Uses reflection to determine if the object is a valid type.
+ if (originalValue instanceof Integer) {
+ result = ((Integer) originalValue).floatValue();
+ } else if (originalValue instanceof Double){
+ result = ((Double) originalValue).floatValue();
+ } else if (originalValue instanceof Float){
+ result = (Float) originalValue;
+ }
+
+ return result;
+ }
+}
diff --git a/base/src/main/java/com/smartdevicelink/util/Version.java b/base/src/main/java/com/smartdevicelink/util/Version.java
new file mode 100644
index 000000000..78eac8db4
--- /dev/null
+++ b/base/src/main/java/com/smartdevicelink/util/Version.java
@@ -0,0 +1,75 @@
+package com.smartdevicelink.util;
+
+
+public class Version {
+
+ final int major,minor,patch;
+
+ public Version(){
+ major = 0;
+ minor = 0;
+ patch = 0;
+ }
+
+ public Version(int major, int minor, int patch){
+ this.major = major;
+ this.minor = minor;
+ this.patch = patch;
+ }
+
+ public Version(String versionString){
+ String[] versions = versionString.split("\\.");
+ if(versions.length!=3){
+ throw new IllegalArgumentException("Incorrect version string format");
+ }
+ major = Integer.valueOf(versions[0]);
+ minor = Integer.valueOf(versions[1]);
+ patch = Integer.valueOf(versions[2]);
+
+ }
+
+ public int getMajor() {
+ return major;
+ }
+
+ public int getMinor() {
+ return minor;
+ }
+
+ public int getPatch() {
+ return patch;
+ }
+
+ /**
+ * Method to test if this instance of Version is newer than the supplied one.
+ * @param version the version to check against
+ * @return 1 if this instance is newer, -1 if supplied version is newer, and 0 if they are equal
+ */
+ public int isNewerThan(Version version){
+ if(this.major > version.major){
+ return 1;
+ }else if(this.major == version.major){
+ if(this.minor > version.minor){
+ return 1;
+ } else if(this.minor == version.minor){
+ if(this.patch > version.patch){
+ return 1;
+ }else if(this.patch == version.patch){
+ return 0;
+ }
+ }
+ }
+ return -1;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append(major);
+ builder.append(".");
+ builder.append(minor);
+ builder.append(".");
+ builder.append(patch);
+ return builder.toString();
+ }
+}
diff --git a/base/src/main/java/org/json/JSON.java b/base/src/main/java/org/json/JSON.java
new file mode 100644
index 000000000..1b32e698d
--- /dev/null
+++ b/base/src/main/java/org/json/JSON.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 org.json;
+
+class JSON {
+ /**
+ * Returns the input if it is a JSON-permissible value; throws otherwise.
+ */
+ static double checkDouble(double d) throws JSONException {
+ if (Double.isInfinite(d) || Double.isNaN(d)) {
+ throw new JSONException("Forbidden numeric value: " + d);
+ }
+ return d;
+ }
+
+ static Boolean toBoolean(Object value) {
+ if (value instanceof Boolean) {
+ return (Boolean) value;
+ } else if (value instanceof String) {
+ String stringValue = (String) value;
+ if ("true".equalsIgnoreCase(stringValue)) {
+ return true;
+ } else if ("false".equalsIgnoreCase(stringValue)) {
+ return false;
+ }
+ }
+ return null;
+ }
+
+ static Double toDouble(Object value) {
+ if (value instanceof Double) {
+ return (Double) value;
+ } else if (value instanceof Number) {
+ return ((Number) value).doubleValue();
+ } else if (value instanceof String) {
+ try {
+ return Double.valueOf((String) value);
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ return null;
+ }
+
+ static Integer toInteger(Object value) {
+ if (value instanceof Integer) {
+ return (Integer) value;
+ } else if (value instanceof Number) {
+ return ((Number) value).intValue();
+ } else if (value instanceof String) {
+ try {
+ return (int) Double.parseDouble((String) value);
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ return null;
+ }
+
+ static Long toLong(Object value) {
+ if (value instanceof Long) {
+ return (Long) value;
+ } else if (value instanceof Number) {
+ return ((Number) value).longValue();
+ } else if (value instanceof String) {
+ try {
+ return (long) Double.parseDouble((String) value);
+ } catch (NumberFormatException ignored) {
+ }
+ }
+ return null;
+ }
+
+ static String toString(Object value) {
+ if (value instanceof String) {
+ return (String) value;
+ } else if (value != null) {
+ return String.valueOf(value);
+ }
+ return null;
+ }
+
+ public static JSONException typeMismatch(Object indexOrName, Object actual,
+ String requiredType) throws JSONException {
+ if (actual == null) {
+ throw new JSONException("Value at " + indexOrName + " is null.");
+ } else {
+ throw new JSONException("Value " + actual + " at " + indexOrName
+ + " of type " + actual.getClass().getName()
+ + " cannot be converted to " + requiredType);
+ }
+ }
+
+ public static JSONException typeMismatch(Object actual, String requiredType)
+ throws JSONException {
+ if (actual == null) {
+ throw new JSONException("Value is null.");
+ } else {
+ throw new JSONException("Value " + actual
+ + " of type " + actual.getClass().getName()
+ + " cannot be converted to " + requiredType);
+ }
+ }
+}
diff --git a/base/src/main/java/org/json/JSONArray.java b/base/src/main/java/org/json/JSONArray.java
new file mode 100644
index 000000000..996f44909
--- /dev/null
+++ b/base/src/main/java/org/json/JSONArray.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 org.json;
+
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ * A dense indexed sequence of values. Values may be any mix of
+ * {@link JSONObject JSONObjects}, other {@link JSONArray JSONArrays}, Strings,
+ * Booleans, Integers, Longs, Doubles, {@code null} or {@link JSONObject#NULL}.
+ * Values may not be {@link Double#isNaN() NaNs}, {@link Double#isInfinite()
+ * infinities}, or of any type not listed here.
+ *
+ * <p>{@code JSONArray} has the same type coercion behavior and
+ * optional/mandatory accessors as {@link JSONObject}. See that class'
+ * documentation for details.
+ *
+ * <p><strong>Warning:</strong> this class represents null in two incompatible
+ * ways: the standard Java {@code null} reference, and the sentinel value {@link
+ * JSONObject#NULL}. In particular, {@code get} fails if the requested index
+ * holds the null reference, but succeeds if it holds {@code JSONObject.NULL}.
+ *
+ * <p>Instances of this class are not thread safe. Although this class is
+ * nonfinal, it was not designed for inheritance and should not be subclassed.
+ * In particular, self-use by overridable methods is not specified. See
+ * <i>Effective Java</i> Item 17, "Design and Document or inheritance or else
+ * prohibit it" for further information.
+ */
+public class JSONArray {
+
+ private final List<Object> values;
+
+ /**
+ * Creates a {@code JSONArray} with no values.
+ */
+ public JSONArray() {
+ values = new ArrayList<Object>();
+ }
+
+ /**
+ * Creates a new {@code JSONArray} by copying all values from the given
+ * collection.
+ *
+ * @param copyFrom a collection whose values are of supported types.
+ * Unsupported values are not permitted and will yield an array in an
+ * inconsistent state.
+ */
+ /* Accept a raw type for API compatibility */
+ public JSONArray(Collection copyFrom) {
+ this();
+ if (copyFrom != null) {
+ for (Iterator it = copyFrom.iterator(); it.hasNext();) {
+ put(JSONObject.wrap(it.next()));
+ }
+ }
+ }
+
+ /**
+ * Creates a new {@code JSONArray} with values from the next array in the
+ * tokener.
+ *
+ * @param readFrom a tokener whose nextValue() method will yield a
+ * {@code JSONArray}.
+ * @throws JSONException if the parse fails or doesn't yield a
+ * {@code JSONArray}.
+ */
+ public JSONArray(JSONTokener readFrom) throws JSONException {
+ /*
+ * Getting the parser to populate this could get tricky. Instead, just
+ * parse to temporary JSONArray and then steal the data from that.
+ */
+ Object object = readFrom.nextValue();
+ if (object instanceof JSONArray) {
+ values = ((JSONArray) object).values;
+ } else {
+ throw JSON.typeMismatch(object, "JSONArray");
+ }
+ }
+
+ /**
+ * Creates a new {@code JSONArray} with values from the JSON string.
+ *
+ * @param json a JSON-encoded string containing an array.
+ * @throws JSONException if the parse fails or doesn't yield a {@code
+ * JSONArray}.
+ */
+ public JSONArray(String json) throws JSONException {
+ this(new JSONTokener(json));
+ }
+
+ /**
+ * Creates a new {@code JSONArray} with values from the given primitive array.
+ */
+ public JSONArray(Object array) throws JSONException {
+ if (!array.getClass().isArray()) {
+ throw new JSONException("Not a primitive array: " + array.getClass());
+ }
+ final int length = Array.getLength(array);
+ values = new ArrayList<Object>(length);
+ for (int i = 0; i < length; ++i) {
+ put(JSONObject.wrap(Array.get(array, i)));
+ }
+ }
+
+ /**
+ * Returns the number of values in this array.
+ */
+ public int length() {
+ return values.size();
+ }
+
+ /**
+ * Appends {@code value} to the end of this array.
+ *
+ * @return this array.
+ */
+ public JSONArray put(boolean value) {
+ values.add(value);
+ return this;
+ }
+
+ /**
+ * Appends {@code value} to the end of this array.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this array.
+ */
+ public JSONArray put(double value) throws JSONException {
+ values.add(JSON.checkDouble(value));
+ return this;
+ }
+
+ /**
+ * Appends {@code value} to the end of this array.
+ *
+ * @return this array.
+ */
+ public JSONArray put(int value) {
+ values.add(value);
+ return this;
+ }
+
+ /**
+ * Appends {@code value} to the end of this array.
+ *
+ * @return this array.
+ */
+ public JSONArray put(long value) {
+ values.add(value);
+ return this;
+ }
+
+ /**
+ * Appends {@code value} to the end of this array.
+ *
+ * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+ * Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May
+ * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
+ * infinities}. Unsupported values are not permitted and will cause the
+ * array to be in an inconsistent state.
+ * @return this array.
+ */
+ public JSONArray put(Object value) {
+ values.add(value);
+ return this;
+ }
+
+ /**
+ * Same as {@link #put}, with added validity checks.
+ */
+ void checkedPut(Object value) throws JSONException {
+ if (value instanceof Number) {
+ JSON.checkDouble(((Number) value).doubleValue());
+ }
+
+ put(value);
+ }
+
+ /**
+ * Sets the value at {@code index} to {@code value}, null padding this array
+ * to the required length if necessary. If a value already exists at {@code
+ * index}, it will be replaced.
+ *
+ * @return this array.
+ */
+ public JSONArray put(int index, boolean value) throws JSONException {
+ return put(index, (Boolean) value);
+ }
+
+ /**
+ * Sets the value at {@code index} to {@code value}, null padding this array
+ * to the required length if necessary. If a value already exists at {@code
+ * index}, it will be replaced.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this array.
+ */
+ public JSONArray put(int index, double value) throws JSONException {
+ return put(index, (Double) value);
+ }
+
+ /**
+ * Sets the value at {@code index} to {@code value}, null padding this array
+ * to the required length if necessary. If a value already exists at {@code
+ * index}, it will be replaced.
+ *
+ * @return this array.
+ */
+ public JSONArray put(int index, int value) throws JSONException {
+ return put(index, (Integer) value);
+ }
+
+ /**
+ * Sets the value at {@code index} to {@code value}, null padding this array
+ * to the required length if necessary. If a value already exists at {@code
+ * index}, it will be replaced.
+ *
+ * @return this array.
+ */
+ public JSONArray put(int index, long value) throws JSONException {
+ return put(index, (Long) value);
+ }
+
+ /**
+ * Sets the value at {@code index} to {@code value}, null padding this array
+ * to the required length if necessary. If a value already exists at {@code
+ * index}, it will be replaced.
+ *
+ * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+ * Integer, Long, Double, {@link JSONObject#NULL}, or {@code null}. May
+ * not be {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
+ * infinities}.
+ * @return this array.
+ */
+ public JSONArray put(int index, Object value) throws JSONException {
+ if (value instanceof Number) {
+ // deviate from the original by checking all Numbers, not just floats & doubles
+ JSON.checkDouble(((Number) value).doubleValue());
+ }
+ while (values.size() <= index) {
+ values.add(null);
+ }
+ values.set(index, value);
+ return this;
+ }
+
+ /**
+ * Returns true if this array has no value at {@code index}, or if its value
+ * is the {@code null} reference or {@link JSONObject#NULL}.
+ */
+ public boolean isNull(int index) {
+ Object value = opt(index);
+ return value == null || value == JSONObject.NULL;
+ }
+
+ /**
+ * Returns the value at {@code index}.
+ *
+ * @throws JSONException if this array has no value at {@code index}, or if
+ * that value is the {@code null} reference. This method returns
+ * normally if the value is {@code JSONObject#NULL}.
+ */
+ public Object get(int index) throws JSONException {
+ try {
+ Object value = values.get(index);
+ if (value == null) {
+ throw new JSONException("Value at " + index + " is null.");
+ }
+ return value;
+ } catch (IndexOutOfBoundsException e) {
+ throw new JSONException("Index " + index + " out of range [0.." + values.size() + ")", e);
+ }
+ }
+
+ /**
+ * Returns the value at {@code index}, or null if the array has no value
+ * at {@code index}.
+ */
+ public Object opt(int index) {
+ if (index < 0 || index >= values.size()) {
+ return null;
+ }
+ return values.get(index);
+ }
+
+ /**
+ * Removes and returns the value at {@code index}, or null if the array has no value
+ * at {@code index}.
+ */
+ public Object remove(int index) {
+ if (index < 0 || index >= values.size()) {
+ return null;
+ }
+ return values.remove(index);
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a boolean or can
+ * be coerced to a boolean.
+ *
+ * @throws JSONException if the value at {@code index} doesn't exist or
+ * cannot be coerced to a boolean.
+ */
+ public boolean getBoolean(int index) throws JSONException {
+ Object object = get(index);
+ Boolean result = JSON.toBoolean(object);
+ if (result == null) {
+ throw JSON.typeMismatch(index, object, "boolean");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a boolean or can
+ * be coerced to a boolean. Returns false otherwise.
+ */
+ public boolean optBoolean(int index) {
+ return optBoolean(index, false);
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a boolean or can
+ * be coerced to a boolean. Returns {@code fallback} otherwise.
+ */
+ public boolean optBoolean(int index, boolean fallback) {
+ Object object = opt(index);
+ Boolean result = JSON.toBoolean(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a double or can
+ * be coerced to a double.
+ *
+ * @throws JSONException if the value at {@code index} doesn't exist or
+ * cannot be coerced to a double.
+ */
+ public double getDouble(int index) throws JSONException {
+ Object object = get(index);
+ Double result = JSON.toDouble(object);
+ if (result == null) {
+ throw JSON.typeMismatch(index, object, "double");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a double or can
+ * be coerced to a double. Returns {@code NaN} otherwise.
+ */
+ public double optDouble(int index) {
+ return optDouble(index, Double.NaN);
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a double or can
+ * be coerced to a double. Returns {@code fallback} otherwise.
+ */
+ public double optDouble(int index, double fallback) {
+ Object object = opt(index);
+ Double result = JSON.toDouble(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is an int or
+ * can be coerced to an int.
+ *
+ * @throws JSONException if the value at {@code index} doesn't exist or
+ * cannot be coerced to a int.
+ */
+ public int getInt(int index) throws JSONException {
+ Object object = get(index);
+ Integer result = JSON.toInteger(object);
+ if (result == null) {
+ throw JSON.typeMismatch(index, object, "int");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is an int or
+ * can be coerced to an int. Returns 0 otherwise.
+ */
+ public int optInt(int index) {
+ return optInt(index, 0);
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is an int or
+ * can be coerced to an int. Returns {@code fallback} otherwise.
+ */
+ public int optInt(int index, int fallback) {
+ Object object = opt(index);
+ Integer result = JSON.toInteger(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a long or
+ * can be coerced to a long.
+ *
+ * @throws JSONException if the value at {@code index} doesn't exist or
+ * cannot be coerced to a long.
+ */
+ public long getLong(int index) throws JSONException {
+ Object object = get(index);
+ Long result = JSON.toLong(object);
+ if (result == null) {
+ throw JSON.typeMismatch(index, object, "long");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a long or
+ * can be coerced to a long. Returns 0 otherwise.
+ */
+ public long optLong(int index) {
+ return optLong(index, 0L);
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a long or
+ * can be coerced to a long. Returns {@code fallback} otherwise.
+ */
+ public long optLong(int index, long fallback) {
+ Object object = opt(index);
+ Long result = JSON.toLong(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists, coercing it if
+ * necessary.
+ *
+ * @throws JSONException if no such value exists.
+ */
+ public String getString(int index) throws JSONException {
+ Object object = get(index);
+ String result = JSON.toString(object);
+ if (result == null) {
+ throw JSON.typeMismatch(index, object, "String");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists, coercing it if
+ * necessary. Returns the empty string if no such value exists.
+ */
+ public String optString(int index) {
+ return optString(index, "");
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists, coercing it if
+ * necessary. Returns {@code fallback} if no such value exists.
+ */
+ public String optString(int index, String fallback) {
+ Object object = opt(index);
+ String result = JSON.toString(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a {@code
+ * JSONArray}.
+ *
+ * @throws JSONException if the value doesn't exist or is not a {@code
+ * JSONArray}.
+ */
+ public JSONArray getJSONArray(int index) throws JSONException {
+ Object object = get(index);
+ if (object instanceof JSONArray) {
+ return (JSONArray) object;
+ } else {
+ throw JSON.typeMismatch(index, object, "JSONArray");
+ }
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a {@code
+ * JSONArray}. Returns null otherwise.
+ */
+ public JSONArray optJSONArray(int index) {
+ Object object = opt(index);
+ return object instanceof JSONArray ? (JSONArray) object : null;
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a {@code
+ * JSONObject}.
+ *
+ * @throws JSONException if the value doesn't exist or is not a {@code
+ * JSONObject}.
+ */
+ public JSONObject getJSONObject(int index) throws JSONException {
+ Object object = get(index);
+ if (object instanceof JSONObject) {
+ return (JSONObject) object;
+ } else {
+ throw JSON.typeMismatch(index, object, "JSONObject");
+ }
+ }
+
+ /**
+ * Returns the value at {@code index} if it exists and is a {@code
+ * JSONObject}. Returns null otherwise.
+ */
+ public JSONObject optJSONObject(int index) {
+ Object object = opt(index);
+ return object instanceof JSONObject ? (JSONObject) object : null;
+ }
+
+ /**
+ * Returns a new object whose values are the values in this array, and whose
+ * names are the values in {@code names}. Names and values are paired up by
+ * index from 0 through to the shorter array's length. Names that are not
+ * strings will be coerced to strings. This method returns null if either
+ * array is empty.
+ */
+ public JSONObject toJSONObject(JSONArray names) throws JSONException {
+ JSONObject result = new JSONObject();
+ int length = Math.min(names.length(), values.size());
+ if (length == 0) {
+ return null;
+ }
+ for (int i = 0; i < length; i++) {
+ String name = JSON.toString(names.opt(i));
+ result.put(name, opt(i));
+ }
+ return result;
+ }
+
+ /**
+ * Returns a new string by alternating this array's values with {@code
+ * separator}. This array's string values are quoted and have their special
+ * characters escaped. For example, the array containing the strings '12"
+ * pizza', 'taco' and 'soda' joined on '+' returns this:
+ * <pre>"12\" pizza"+"taco"+"soda"</pre>
+ */
+ public String join(String separator) throws JSONException {
+ JSONStringer stringer = new JSONStringer();
+ stringer.open(JSONStringer.Scope.NULL, "");
+ for (int i = 0, size = values.size(); i < size; i++) {
+ if (i > 0) {
+ stringer.out.append(separator);
+ }
+ stringer.value(values.get(i));
+ }
+ stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
+ return stringer.out.toString();
+ }
+
+ /**
+ * Encodes this array as a compact JSON string, such as:
+ * <pre>[94043,90210]</pre>
+ */
+ @Override public String toString() {
+ try {
+ JSONStringer stringer = new JSONStringer();
+ writeTo(stringer);
+ return stringer.toString();
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Encodes this array as a human readable JSON string for debugging, such
+ * as:
+ * <pre>
+ * [
+ * 94043,
+ * 90210
+ * ]</pre>
+ *
+ * @param indentSpaces the number of spaces to indent for each level of
+ * nesting.
+ */
+ public String toString(int indentSpaces) throws JSONException {
+ JSONStringer stringer = new JSONStringer(indentSpaces);
+ writeTo(stringer);
+ return stringer.toString();
+ }
+
+ void writeTo(JSONStringer stringer) throws JSONException {
+ stringer.array();
+ for (Object value : values) {
+ stringer.value(value);
+ }
+ stringer.endArray();
+ }
+
+ @Override public boolean equals(Object o) {
+ return o instanceof JSONArray && ((JSONArray) o).values.equals(values);
+ }
+
+ @Override public int hashCode() {
+ // diverge from the original, which doesn't implement hashCode
+ return values.hashCode();
+ }
+}
diff --git a/base/src/main/java/org/json/JSONException.java b/base/src/main/java/org/json/JSONException.java
new file mode 100644
index 000000000..05e1dddc9
--- /dev/null
+++ b/base/src/main/java/org/json/JSONException.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 org.json;
+
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ * Thrown to indicate a problem with the JSON API. Such problems include:
+ * <ul>
+ * <li>Attempts to parse or construct malformed documents
+ * <li>Use of null as a name
+ * <li>Use of numeric types not available to JSON, such as {@link
+ * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
+ * <li>Lookups using an out of range index or nonexistent name
+ * <li>Type mismatches on lookups
+ * </ul>
+ *
+ * <p>Although this is a checked exception, it is rarely recoverable. Most
+ * callers should simply wrap this exception in an unchecked exception and
+ * rethrow:
+ * <pre> public JSONArray toJSONObject() {
+ * try {
+ * JSONObject result = new JSONObject();
+ * ...
+ * } catch (JSONException e) {
+ * throw new RuntimeException(e);
+ * }
+ * }</pre>
+ */
+public class JSONException extends Exception {
+
+ public JSONException(String s) {
+ super(s);
+ }
+
+ public JSONException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public JSONException(Throwable cause) {
+ super(cause);
+ }
+
+}
diff --git a/base/src/main/java/org/json/JSONObject.java b/base/src/main/java/org/json/JSONObject.java
new file mode 100644
index 000000000..fe9d0dbd2
--- /dev/null
+++ b/base/src/main/java/org/json/JSONObject.java
@@ -0,0 +1,831 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 org.json;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ * A modifiable set of name/value mappings. Names are unique, non-null strings.
+ * Values may be any mix of {@link JSONObject JSONObjects}, {@link JSONArray
+ * JSONArrays}, Strings, Booleans, Integers, Longs, Doubles or {@link #NULL}.
+ * Values may not be {@code null}, {@link Double#isNaN() NaNs}, {@link
+ * Double#isInfinite() infinities}, or of any type not listed here.
+ *
+ * <p>This class can coerce values to another type when requested.
+ * <ul>
+ * <li>When the requested type is a boolean, strings will be coerced using a
+ * case-insensitive comparison to "true" and "false".
+ * <li>When the requested type is a double, other {@link Number} types will
+ * be coerced using {@link Number#doubleValue() doubleValue}. Strings
+ * that can be coerced using {@link Double#valueOf(String)} will be.
+ * <li>When the requested type is an int, other {@link Number} types will
+ * be coerced using {@link Number#intValue() intValue}. Strings
+ * that can be coerced using {@link Double#valueOf(String)} will be,
+ * and then cast to int.
+ * <li><a name="lossy">When the requested type is a long, other {@link Number} types will
+ * be coerced using {@link Number#longValue() longValue}. Strings
+ * that can be coerced using {@link Double#valueOf(String)} will be,
+ * and then cast to long. This two-step conversion is lossy for very
+ * large values. For example, the string "9223372036854775806" yields the
+ * long 9223372036854775807.</a>
+ * <li>When the requested type is a String, other non-null values will be
+ * coerced using {@link String#valueOf(Object)}. Although null cannot be
+ * coerced, the sentinel value {@link JSONObject#NULL} is coerced to the
+ * string "null".
+ * </ul>
+ *
+ * <p>This class can look up both mandatory and optional values:
+ * <ul>
+ * <li>Use <code>get<i>Type</i>()</code> to retrieve a mandatory value. This
+ * fails with a {@code JSONException} if the requested name has no value
+ * or if the value cannot be coerced to the requested type.
+ * <li>Use <code>opt<i>Type</i>()</code> to retrieve an optional value. This
+ * returns a system- or user-supplied default if the requested name has no
+ * value or if the value cannot be coerced to the requested type.
+ * </ul>
+ *
+ * <p><strong>Warning:</strong> this class represents null in two incompatible
+ * ways: the standard Java {@code null} reference, and the sentinel value {@link
+ * JSONObject#NULL}. In particular, calling {@code put(name, null)} removes the
+ * named entry from the object but {@code put(name, JSONObject.NULL)} stores an
+ * entry whose value is {@code JSONObject.NULL}.
+ *
+ * <p>Instances of this class are not thread safe. Although this class is
+ * nonfinal, it was not designed for inheritance and should not be subclassed.
+ * In particular, self-use by overrideable methods is not specified. See
+ * <i>Effective Java</i> Item 17, "Design and Document or inheritance or else
+ * prohibit it" for further information.
+ */
+public class JSONObject {
+
+ private static final Double NEGATIVE_ZERO = -0d;
+
+ /**
+ * A sentinel value used to explicitly define a name with no value. Unlike
+ * {@code null}, names with this value:
+ * <ul>
+ * <li>show up in the {@link #names} array
+ * <li>show up in the {@link #keys} iterator
+ * <li>return {@code true} for {@link #has(String)}
+ * <li>do not throw on {@link #get(String)}
+ * <li>are included in the encoded JSON string.
+ * </ul>
+ *
+ * <p>This value violates the general contract of {@link Object#equals} by
+ * returning true when compared to {@code null}. Its {@link #toString}
+ * method returns "null".
+ */
+ @NonNull
+ public static final Object NULL = new Object() {
+ @Override public boolean equals(Object o) {
+ return o == this || o == null; // API specifies this broken equals implementation
+ }
+ // at least make the broken equals(null) consistent with Objects.hashCode(null).
+ @Override public int hashCode() { return Objects.hashCode(null); }
+ @Override public String toString() {
+ return "null";
+ }
+ };
+
+
+ private final LinkedHashMap<String, Object> nameValuePairs;
+
+ /**
+ * Creates a {@code JSONObject} with no name/value mappings.
+ */
+ public JSONObject() {
+ nameValuePairs = new LinkedHashMap<String, Object>();
+ }
+
+ /**
+ * Creates a new {@code JSONObject} by copying all name/value mappings from
+ * the given map.
+ *
+ * @param copyFrom a map whose keys are of type {@link String} and whose
+ * values are of supported types.
+ * @throws NullPointerException if any of the map's keys are null.
+ */
+ /* (accept a raw type for API compatibility) */
+ public JSONObject(@NonNull Map copyFrom) {
+ this();
+ Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom;
+ for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) {
+ /*
+ * Deviate from the original by checking that keys are non-null and
+ * of the proper type. (We still defer validating the values).
+ */
+ String key = (String) entry.getKey();
+ if (key == null) {
+ throw new NullPointerException("key == null");
+ }
+ nameValuePairs.put(key, wrap(entry.getValue()));
+ }
+ }
+
+ /**
+ * Creates a new {@code JSONObject} with name/value mappings from the next
+ * object in the tokener.
+ *
+ * @param readFrom a tokener whose nextValue() method will yield a
+ * {@code JSONObject}.
+ * @throws JSONException if the parse fails or doesn't yield a
+ * {@code JSONObject}.
+ */
+ public JSONObject(@NonNull JSONTokener readFrom) throws JSONException {
+ /*
+ * Getting the parser to populate this could get tricky. Instead, just
+ * parse to temporary JSONObject and then steal the data from that.
+ */
+ Object object = readFrom.nextValue();
+ if (object instanceof JSONObject) {
+ this.nameValuePairs = ((JSONObject) object).nameValuePairs;
+ } else {
+ throw JSON.typeMismatch(object, "JSONObject");
+ }
+ }
+
+ /**
+ * Creates a new {@code JSONObject} with name/value mappings from the JSON
+ * string.
+ *
+ * @param json a JSON-encoded string containing an object.
+ * @throws JSONException if the parse fails or doesn't yield a {@code
+ * JSONObject}.
+ */
+ public JSONObject(@NonNull String json) throws JSONException {
+ this(new JSONTokener(json));
+ }
+
+ /**
+ * Creates a new {@code JSONObject} by copying mappings for the listed names
+ * from the given object. Names that aren't present in {@code copyFrom} will
+ * be skipped.
+ */
+ public JSONObject(@NonNull JSONObject copyFrom, @NonNull String [] names) throws JSONException {
+ this();
+ for (String name : names) {
+ Object value = copyFrom.opt(name);
+ if (value != null) {
+ nameValuePairs.put(name, value);
+ }
+ }
+ }
+
+ /**
+ * Returns the number of name/value mappings in this object.
+ */
+ public int length() {
+ return nameValuePairs.size();
+ }
+
+ /**
+ * Maps {@code name} to {@code value}, clobbering any existing name/value
+ * mapping with the same name.
+ *
+ * @return this object.
+ */
+ @NonNull public JSONObject put(@NonNull String name, boolean value) throws JSONException {
+ nameValuePairs.put(checkName(name), value);
+ return this;
+ }
+
+ /**
+ * Maps {@code name} to {@code value}, clobbering any existing name/value
+ * mapping with the same name.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this object.
+ */
+ @NonNull public JSONObject put(@NonNull String name, double value) throws JSONException {
+ nameValuePairs.put(checkName(name), JSON.checkDouble(value));
+ return this;
+ }
+
+ /**
+ * Maps {@code name} to {@code value}, clobbering any existing name/value
+ * mapping with the same name.
+ *
+ * @return this object.
+ */
+ @NonNull public JSONObject put(@NonNull String name, int value) throws JSONException {
+ nameValuePairs.put(checkName(name), value);
+ return this;
+ }
+
+ /**
+ * Maps {@code name} to {@code value}, clobbering any existing name/value
+ * mapping with the same name.
+ *
+ * @return this object.
+ */
+ @NonNull public JSONObject put(@NonNull String name, long value) throws JSONException {
+ nameValuePairs.put(checkName(name), value);
+ return this;
+ }
+
+ /**
+ * Maps {@code name} to {@code value}, clobbering any existing name/value
+ * mapping with the same name. If the value is {@code null}, any existing
+ * mapping for {@code name} is removed.
+ *
+ * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+ * Integer, Long, Double, {@link #NULL}, or {@code null}. May not be
+ * {@link Double#isNaN() NaNs} or {@link Double#isInfinite()
+ * infinities}.
+ * @return this object.
+ */
+ @NonNull public JSONObject put(@NonNull String name, @Nullable Object value) throws JSONException {
+ if (value == null) {
+ nameValuePairs.remove(name);
+ return this;
+ }
+ if (value instanceof Number) {
+ // deviate from the original by checking all Numbers, not just floats & doubles
+ JSON.checkDouble(((Number) value).doubleValue());
+ }
+ nameValuePairs.put(checkName(name), value);
+ return this;
+ }
+
+ /**
+ * Equivalent to {@code put(name, value)} when both parameters are non-null;
+ * does nothing otherwise.
+ */
+ @NonNull public JSONObject putOpt(@Nullable String name, @Nullable Object value) throws JSONException {
+ if (name == null || value == null) {
+ return this;
+ }
+ return put(name, value);
+ }
+
+ /**
+ * Appends {@code value} to the array already mapped to {@code name}. If
+ * this object has no mapping for {@code name}, this inserts a new mapping.
+ * If the mapping exists but its value is not an array, the existing
+ * and new values are inserted in order into a new array which is itself
+ * mapped to {@code name}. In aggregate, this allows values to be added to a
+ * mapping one at a time.
+ *
+ * <p> Note that {@code append(String, Object)} provides better semantics.
+ * In particular, the mapping for {@code name} will <b>always</b> be a
+ * {@link JSONArray}. Using {@code accumulate} will result in either a
+ * {@link JSONArray} or a mapping whose type is the type of {@code value}
+ * depending on the number of calls to it.
+ *
+ * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+ * Integer, Long, Double, {@link #NULL} or null. May not be {@link
+ * Double#isNaN() NaNs} or {@link Double#isInfinite() infinities}.
+ */
+ // TODO: Change {@code append) to {@link #append} when append is
+ // unhidden.
+ @NonNull public JSONObject accumulate(@NonNull String name, @Nullable Object value) throws JSONException {
+ Object current = nameValuePairs.get(checkName(name));
+ if (current == null) {
+ return put(name, value);
+ }
+
+ if (current instanceof JSONArray) {
+ JSONArray array = (JSONArray) current;
+ array.checkedPut(value);
+ } else {
+ JSONArray array = new JSONArray();
+ array.checkedPut(current);
+ array.checkedPut(value);
+ nameValuePairs.put(name, array);
+ }
+ return this;
+ }
+
+ /**
+ * Appends values to the array mapped to {@code name}. A new {@link JSONArray}
+ * mapping for {@code name} will be inserted if no mapping exists. If the existing
+ * mapping for {@code name} is not a {@link JSONArray}, a {@link JSONException}
+ * will be thrown.
+ *
+ * @throws JSONException if {@code name} is {@code null} or if the mapping for
+ * {@code name} is non-null and is not a {@link JSONArray}.
+ *
+ * @hide
+ */
+ public JSONObject append(String name, Object value) throws JSONException {
+ Object current = nameValuePairs.get(checkName(name));
+
+ final JSONArray array;
+ if (current instanceof JSONArray) {
+ array = (JSONArray) current;
+ } else if (current == null) {
+ JSONArray newArray = new JSONArray();
+ nameValuePairs.put(name, newArray);
+ array = newArray;
+ } else {
+ throw new JSONException("Key " + name + " is not a JSONArray");
+ }
+
+ array.checkedPut(value);
+
+ return this;
+ }
+
+ String checkName(String name) throws JSONException {
+ if (name == null) {
+ throw new JSONException("Names must be non-null");
+ }
+ return name;
+ }
+
+ /**
+ * Removes the named mapping if it exists; does nothing otherwise.
+ *
+ * @return the value previously mapped by {@code name}, or null if there was
+ * no such mapping.
+ */
+ @Nullable public Object remove(@Nullable String name) {
+ return nameValuePairs.remove(name);
+ }
+
+ /**
+ * Returns true if this object has no mapping for {@code name} or if it has
+ * a mapping whose value is {@link #NULL}.
+ */
+ public boolean isNull(@Nullable String name) {
+ Object value = nameValuePairs.get(name);
+ return value == null || value == NULL;
+ }
+
+ /**
+ * Returns true if this object has a mapping for {@code name}. The mapping
+ * may be {@link #NULL}.
+ */
+ public boolean has(@Nullable String name) {
+ return nameValuePairs.containsKey(name);
+ }
+
+ /**
+ * Returns the value mapped by {@code name}, or throws if no such mapping exists.
+ *
+ * @throws JSONException if no such mapping exists.
+ */
+ @NonNull public Object get(@NonNull String name) throws JSONException {
+ Object result = nameValuePairs.get(name);
+ if (result == null) {
+ throw new JSONException("No value for " + name);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value mapped by {@code name}, or null if no such mapping
+ * exists.
+ */
+ @Nullable public Object opt(@Nullable String name) {
+ return nameValuePairs.get(name);
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a boolean or
+ * can be coerced to a boolean, or throws otherwise.
+ *
+ * @throws JSONException if the mapping doesn't exist or cannot be coerced
+ * to a boolean.
+ */
+ public boolean getBoolean(@NonNull String name) throws JSONException {
+ Object object = get(name);
+ Boolean result = JSON.toBoolean(object);
+ if (result == null) {
+ throw JSON.typeMismatch(name, object, "boolean");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a boolean or
+ * can be coerced to a boolean, or false otherwise.
+ */
+ public boolean optBoolean(@Nullable String name) {
+ return optBoolean(name, false);
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a boolean or
+ * can be coerced to a boolean, or {@code fallback} otherwise.
+ */
+ public boolean optBoolean(@Nullable String name, boolean fallback) {
+ Object object = opt(name);
+ Boolean result = JSON.toBoolean(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a double or
+ * can be coerced to a double, or throws otherwise.
+ *
+ * @throws JSONException if the mapping doesn't exist or cannot be coerced
+ * to a double.
+ */
+ public double getDouble(@NonNull String name) throws JSONException {
+ Object object = get(name);
+ Double result = JSON.toDouble(object);
+ if (result == null) {
+ throw JSON.typeMismatch(name, object, "double");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a double or
+ * can be coerced to a double, or {@code NaN} otherwise.
+ */
+ public double optDouble(@Nullable String name) {
+ return optDouble(name, Double.NaN);
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a double or
+ * can be coerced to a double, or {@code fallback} otherwise.
+ */
+ public double optDouble(@Nullable String name, double fallback) {
+ Object object = opt(name);
+ Double result = JSON.toDouble(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is an int or
+ * can be coerced to an int, or throws otherwise.
+ *
+ * @throws JSONException if the mapping doesn't exist or cannot be coerced
+ * to an int.
+ */
+ public int getInt(@NonNull String name) throws JSONException {
+ Object object = get(name);
+ Integer result = JSON.toInteger(object);
+ if (result == null) {
+ throw JSON.typeMismatch(name, object, "int");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is an int or
+ * can be coerced to an int, or 0 otherwise.
+ */
+ public int optInt(@Nullable String name) {
+ return optInt(name, 0);
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is an int or
+ * can be coerced to an int, or {@code fallback} otherwise.
+ */
+ public int optInt(@Nullable String name, int fallback) {
+ Object object = opt(name);
+ Integer result = JSON.toInteger(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a long or
+ * can be coerced to a long, or throws otherwise.
+ * Note that JSON represents numbers as doubles,
+ * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
+ *
+ * @throws JSONException if the mapping doesn't exist or cannot be coerced
+ * to a long.
+ */
+ public long getLong(@NonNull String name) throws JSONException {
+ Object object = get(name);
+ Long result = JSON.toLong(object);
+ if (result == null) {
+ throw JSON.typeMismatch(name, object, "long");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a long or
+ * can be coerced to a long, or 0 otherwise. Note that JSON represents numbers as doubles,
+ * so this is <a href="#lossy">lossy</a>; use strings to transfer numbers via JSON.
+ */
+ public long optLong(@Nullable String name) {
+ return optLong(name, 0L);
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a long or
+ * can be coerced to a long, or {@code fallback} otherwise. Note that JSON represents
+ * numbers as doubles, so this is <a href="#lossy">lossy</a>; use strings to transfer
+ * numbers via JSON.
+ */
+ public long optLong(@Nullable String name, long fallback) {
+ Object object = opt(name);
+ Long result = JSON.toLong(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists, coercing it if
+ * necessary, or throws if no such mapping exists.
+ *
+ * @throws JSONException if no such mapping exists.
+ */
+ @NonNull public String getString(@NonNull String name) throws JSONException {
+ Object object = get(name);
+ String result = JSON.toString(object);
+ if (result == null) {
+ throw JSON.typeMismatch(name, object, "String");
+ }
+ return result;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists, coercing it if
+ * necessary, or the empty string if no such mapping exists.
+ */
+ @NonNull public String optString(@Nullable String name) {
+ return optString(name, "");
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists, coercing it if
+ * necessary, or {@code fallback} if no such mapping exists.
+ */
+ @NonNull public String optString(@Nullable String name, @NonNull String fallback) {
+ Object object = opt(name);
+ String result = JSON.toString(object);
+ return result != null ? result : fallback;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a {@code
+ * JSONArray}, or throws otherwise.
+ *
+ * @throws JSONException if the mapping doesn't exist or is not a {@code
+ * JSONArray}.
+ */
+ @NonNull public JSONArray getJSONArray(@NonNull String name) throws JSONException {
+ Object object = get(name);
+ if (object instanceof JSONArray) {
+ return (JSONArray) object;
+ } else {
+ throw JSON.typeMismatch(name, object, "JSONArray");
+ }
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a {@code
+ * JSONArray}, or null otherwise.
+ */
+ @Nullable public JSONArray optJSONArray(@Nullable String name) {
+ Object object = opt(name);
+ return object instanceof JSONArray ? (JSONArray) object : null;
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a {@code
+ * JSONObject}, or throws otherwise.
+ *
+ * @throws JSONException if the mapping doesn't exist or is not a {@code
+ * JSONObject}.
+ */
+ @NonNull public JSONObject getJSONObject(@NonNull String name) throws JSONException {
+ Object object = get(name);
+ if (object instanceof JSONObject) {
+ return (JSONObject) object;
+ } else {
+ throw JSON.typeMismatch(name, object, "JSONObject");
+ }
+ }
+
+ /**
+ * Returns the value mapped by {@code name} if it exists and is a {@code
+ * JSONObject}, or null otherwise.
+ */
+ @Nullable public JSONObject optJSONObject(@Nullable String name) {
+ Object object = opt(name);
+ return object instanceof JSONObject ? (JSONObject) object : null;
+ }
+
+ /**
+ * Returns an array with the values corresponding to {@code names}. The
+ * array contains null for names that aren't mapped. This method returns
+ * null if {@code names} is either null or empty.
+ */
+ @Nullable public JSONArray toJSONArray(@Nullable JSONArray names) throws JSONException {
+ JSONArray result = new JSONArray();
+ if (names == null) {
+ return null;
+ }
+ int length = names.length();
+ if (length == 0) {
+ return null;
+ }
+ for (int i = 0; i < length; i++) {
+ String name = JSON.toString(names.opt(i));
+ result.put(opt(name));
+ }
+ return result;
+ }
+
+ /**
+ * Returns an iterator of the {@code String} names in this object. The
+ * returned iterator supports {@link Iterator#remove() remove}, which will
+ * remove the corresponding mapping from this object. If this object is
+ * modified after the iterator is returned, the iterator's behavior is
+ * undefined. The order of the keys is undefined.
+ */
+ @NonNull public Iterator<String> keys() {
+ return nameValuePairs.keySet().iterator();
+ }
+
+ /**
+ * Returns the set of {@code String} names in this object. The returned set
+ * is a view of the keys in this object. {@link Set#remove(Object)} will remove
+ * the corresponding mapping from this object and set iterator behaviour
+ * is undefined if this object is modified after it is returned.
+ *
+ * See {@link #keys()}.
+ *
+ * @hide.
+ */
+ public Set<String> keySet() {
+ return nameValuePairs.keySet();
+ }
+
+ /**
+ * Returns an array containing the string names in this object. This method
+ * returns null if this object contains no mappings.
+ */
+ @Nullable public JSONArray names() {
+ return nameValuePairs.isEmpty()
+ ? null
+ : new JSONArray(new ArrayList<String>(nameValuePairs.keySet()));
+ }
+
+ /**
+ * Encodes this object as a compact JSON string, such as:
+ * <pre>{"query":"Pizza","locations":[94043,90210]}</pre>
+ */
+ @Override @NonNull public String toString() {
+ try {
+ JSONStringer stringer = new JSONStringer();
+ writeTo(stringer);
+ return stringer.toString();
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Encodes this object as a human readable JSON string for debugging, such
+ * as:
+ * <pre>
+ * {
+ * "query": "Pizza",
+ * "locations": [
+ * 94043,
+ * 90210
+ * ]
+ * }</pre>
+ *
+ * @param indentSpaces the number of spaces to indent for each level of
+ * nesting.
+ */
+ @NonNull public String toString(int indentSpaces) throws JSONException {
+ JSONStringer stringer = new JSONStringer(indentSpaces);
+ writeTo(stringer);
+ return stringer.toString();
+ }
+
+
+ void writeTo(JSONStringer stringer) throws JSONException {
+ stringer.object();
+ for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) {
+ stringer.key(entry.getKey()).value(entry.getValue());
+ }
+ stringer.endObject();
+ }
+
+ /**
+ * Encodes the number as a JSON string.
+ *
+ * @param number a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ */
+ @NonNull public static String numberToString(@NonNull Number number) throws JSONException {
+ if (number == null) {
+ throw new JSONException("Number must be non-null");
+ }
+
+ double doubleValue = number.doubleValue();
+ JSON.checkDouble(doubleValue);
+
+ // the original returns "-0" instead of "-0.0" for negative zero
+ if (number.equals(NEGATIVE_ZERO)) {
+ return "-0";
+ }
+
+ long longValue = number.longValue();
+ if (doubleValue == (double) longValue) {
+ return Long.toString(longValue);
+ }
+
+ return number.toString();
+ }
+
+ /**
+ * Encodes {@code data} as a JSON string. This applies quotes and any
+ * necessary character escaping.
+ *
+ * @param data the string to encode. Null will be interpreted as an empty
+ * string.
+ */
+ @NonNull public static String quote(@Nullable String data) {
+ if (data == null) {
+ return "\"\"";
+ }
+ try {
+ JSONStringer stringer = new JSONStringer();
+ stringer.open(JSONStringer.Scope.NULL, "");
+ stringer.value(data);
+ stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, "");
+ return stringer.toString();
+ } catch (JSONException e) {
+ throw new AssertionError();
+ }
+ }
+
+ /**
+ * Wraps the given object if necessary.
+ *
+ * <p>If the object is null or , returns {@link #NULL}.
+ * If the object is a {@code JSONArray} or {@code JSONObject}, no wrapping is necessary.
+ * If the object is {@code NULL}, no wrapping is necessary.
+ * If the object is an array or {@code Collection}, returns an equivalent {@code JSONArray}.
+ * If the object is a {@code Map}, returns an equivalent {@code JSONObject}.
+ * If the object is a primitive wrapper type or {@code String}, returns the object.
+ * Otherwise if the object is from a {@code java} package, returns the result of {@code toString}.
+ * If wrapping fails, returns null.
+ */
+ @Nullable public static Object wrap(@Nullable Object o) {
+ if (o == null) {
+ return NULL;
+ }
+ if (o instanceof JSONArray || o instanceof JSONObject) {
+ return o;
+ }
+ if (o.equals(NULL)) {
+ return o;
+ }
+ try {
+ if (o instanceof Collection) {
+ return new JSONArray((Collection) o);
+ } else if (o.getClass().isArray()) {
+ return new JSONArray(o);
+ }
+ if (o instanceof Map) {
+ return new JSONObject((Map) o);
+ }
+ if (o instanceof Boolean ||
+ o instanceof Byte ||
+ o instanceof Character ||
+ o instanceof Double ||
+ o instanceof Float ||
+ o instanceof Integer ||
+ o instanceof Long ||
+ o instanceof Short ||
+ o instanceof String) {
+ return o;
+ }
+ if (o.getClass().getPackage().getName().startsWith("java.")) {
+ return o.toString();
+ }
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+}
diff --git a/base/src/main/java/org/json/JSONStringer.java b/base/src/main/java/org/json/JSONStringer.java
new file mode 100644
index 000000000..dd3b2a7d8
--- /dev/null
+++ b/base/src/main/java/org/json/JSONStringer.java
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 org.json;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ * Implements {@link JSONObject#toString} and {@link JSONArray#toString}. Most
+ * application developers should use those methods directly and disregard this
+ * API. For example:<pre>
+ * JSONObject object = ...
+ * String json = object.toString();</pre>
+ *
+ * <p>Stringers only encode well-formed JSON strings. In particular:
+ * <ul>
+ * <li>The stringer must have exactly one top-level array or object.
+ * <li>Lexical scopes must be balanced: every call to {@link #array} must
+ * have a matching call to {@link #endArray} and every call to {@link
+ * #object} must have a matching call to {@link #endObject}.
+ * <li>Arrays may not contain keys (property names).
+ * <li>Objects must alternate keys (property names) and values.
+ * <li>Values are inserted with either literal {@link #value(Object) value}
+ * calls, or by nesting arrays or objects.
+ * </ul>
+ * Calls that would result in a malformed JSON string will fail with a
+ * {@link JSONException}.
+ *
+ * <p>This class provides no facility for pretty-printing (ie. indenting)
+ * output. To encode indented output, use {@link JSONObject#toString(int)} or
+ * {@link JSONArray#toString(int)}.
+ *
+ * <p>Some implementations of the API support at most 20 levels of nesting.
+ * Attempts to create more than 20 levels of nesting may fail with a {@link
+ * JSONException}.
+ *
+ * <p>Each stringer may be used to encode a single top level value. Instances of
+ * this class are not thread safe. Although this class is nonfinal, it was not
+ * designed for inheritance and should not be subclassed. In particular,
+ * self-use by overrideable methods is not specified. See <i>Effective Java</i>
+ * Item 17, "Design and Document or inheritance or else prohibit it" for further
+ * information.
+ */
+public class JSONStringer {
+
+ /** The output data, containing at most one top-level array or object. */
+ final StringBuilder out = new StringBuilder();
+
+ /**
+ * Lexical scoping elements within this stringer, necessary to insert the
+ * appropriate separator characters (ie. commas and colons) and to detect
+ * nesting errors.
+ */
+ enum Scope {
+
+ /**
+ * An array with no elements requires no separators or newlines before
+ * it is closed.
+ */
+ EMPTY_ARRAY,
+
+ /**
+ * A array with at least one value requires a comma and newline before
+ * the next element.
+ */
+ NONEMPTY_ARRAY,
+
+ /**
+ * An object with no keys or values requires no separators or newlines
+ * before it is closed.
+ */
+ EMPTY_OBJECT,
+
+ /**
+ * An object whose most recent element is a key. The next element must
+ * be a value.
+ */
+ DANGLING_KEY,
+
+ /**
+ * An object with at least one name/value pair requires a comma and
+ * newline before the next element.
+ */
+ NONEMPTY_OBJECT,
+
+ /**
+ * A special bracketless array needed by JSONStringer.join() and
+ * JSONObject.quote() only. Not used for JSON encoding.
+ */
+ NULL,
+ }
+
+ /**
+ * Unlike the original implementation, this stack isn't limited to 20
+ * levels of nesting.
+ */
+ private final List<Scope> stack = new ArrayList<Scope>();
+
+ /**
+ * A string containing a full set of spaces for a single level of
+ * indentation, or null for no pretty printing.
+ */
+ private final String indent;
+
+ public JSONStringer() {
+ indent = null;
+ }
+
+ JSONStringer(int indentSpaces) {
+ char[] indentChars = new char[indentSpaces];
+ Arrays.fill(indentChars, ' ');
+ indent = new String(indentChars);
+ }
+
+ /**
+ * Begins encoding a new array. Each call to this method must be paired with
+ * a call to {@link #endArray}.
+ *
+ * @return this stringer.
+ */
+ public JSONStringer array() throws JSONException {
+ return open(Scope.EMPTY_ARRAY, "[");
+ }
+
+ /**
+ * Ends encoding the current array.
+ *
+ * @return this stringer.
+ */
+ public JSONStringer endArray() throws JSONException {
+ return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]");
+ }
+
+ /**
+ * Begins encoding a new object. Each call to this method must be paired
+ * with a call to {@link #endObject}.
+ *
+ * @return this stringer.
+ */
+ public JSONStringer object() throws JSONException {
+ return open(Scope.EMPTY_OBJECT, "{");
+ }
+
+ /**
+ * Ends encoding the current object.
+ *
+ * @return this stringer.
+ */
+ public JSONStringer endObject() throws JSONException {
+ return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}");
+ }
+
+ /**
+ * Enters a new scope by appending any necessary whitespace and the given
+ * bracket.
+ */
+ JSONStringer open(Scope empty, String openBracket) throws JSONException {
+ if (stack.isEmpty() && out.length() > 0) {
+ throw new JSONException("Nesting problem: multiple top-level roots");
+ }
+ beforeValue();
+ stack.add(empty);
+ out.append(openBracket);
+ return this;
+ }
+
+ /**
+ * Closes the current scope by appending any necessary whitespace and the
+ * given bracket.
+ */
+ JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException {
+ Scope context = peek();
+ if (context != nonempty && context != empty) {
+ throw new JSONException("Nesting problem");
+ }
+
+ stack.remove(stack.size() - 1);
+ if (context == nonempty) {
+ newline();
+ }
+ out.append(closeBracket);
+ return this;
+ }
+
+ /**
+ * Returns the value on the top of the stack.
+ */
+ private Scope peek() throws JSONException {
+ if (stack.isEmpty()) {
+ throw new JSONException("Nesting problem");
+ }
+ return stack.get(stack.size() - 1);
+ }
+
+ /**
+ * Replace the value on the top of the stack with the given value.
+ */
+ private void replaceTop(Scope topOfStack) {
+ stack.set(stack.size() - 1, topOfStack);
+ }
+
+ /**
+ * Encodes {@code value}.
+ *
+ * @param value a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+ * Integer, Long, Double or null. May not be {@link Double#isNaN() NaNs}
+ * or {@link Double#isInfinite() infinities}.
+ * @return this stringer.
+ */
+ public JSONStringer value(Object value) throws JSONException {
+ if (stack.isEmpty()) {
+ throw new JSONException("Nesting problem");
+ }
+
+ if (value instanceof JSONArray) {
+ ((JSONArray) value).writeTo(this);
+ return this;
+
+ } else if (value instanceof JSONObject) {
+ ((JSONObject) value).writeTo(this);
+ return this;
+ }
+
+ beforeValue();
+
+ if (value == null
+ || value instanceof Boolean
+ || value == JSONObject.NULL) {
+ out.append(value);
+
+ } else if (value instanceof Number) {
+ out.append(JSONObject.numberToString((Number) value));
+
+ } else {
+ string(value.toString());
+ }
+
+ return this;
+ }
+
+ /**
+ * Encodes {@code value} to this stringer.
+ *
+ * @return this stringer.
+ */
+ public JSONStringer value(boolean value) throws JSONException {
+ if (stack.isEmpty()) {
+ throw new JSONException("Nesting problem");
+ }
+ beforeValue();
+ out.append(value);
+ return this;
+ }
+
+ /**
+ * Encodes {@code value} to this stringer.
+ *
+ * @param value a finite value. May not be {@link Double#isNaN() NaNs} or
+ * {@link Double#isInfinite() infinities}.
+ * @return this stringer.
+ */
+ public JSONStringer value(double value) throws JSONException {
+ if (stack.isEmpty()) {
+ throw new JSONException("Nesting problem");
+ }
+ beforeValue();
+ out.append(JSONObject.numberToString(value));
+ return this;
+ }
+
+ /**
+ * Encodes {@code value} to this stringer.
+ *
+ * @return this stringer.
+ */
+ public JSONStringer value(long value) throws JSONException {
+ if (stack.isEmpty()) {
+ throw new JSONException("Nesting problem");
+ }
+ beforeValue();
+ out.append(value);
+ return this;
+ }
+
+ private void string(String value) {
+ out.append("\"");
+ for (int i = 0, length = value.length(); i < length; i++) {
+ char c = value.charAt(i);
+
+ /*
+ * From RFC 4627, "All Unicode characters may be placed within the
+ * quotation marks except for the characters that must be escaped:
+ * quotation mark, reverse solidus, and the control characters
+ * (U+0000 through U+001F)."
+ */
+ switch (c) {
+ case '"':
+ case '\\':
+ case '/':
+ out.append('\\').append(c);
+ break;
+
+ case '\t':
+ out.append("\\t");
+ break;
+
+ case '\b':
+ out.append("\\b");
+ break;
+
+ case '\n':
+ out.append("\\n");
+ break;
+
+ case '\r':
+ out.append("\\r");
+ break;
+
+ case '\f':
+ out.append("\\f");
+ break;
+
+ default:
+ if (c <= 0x1F) {
+ out.append(String.format("\\u%04x", (int) c));
+ } else {
+ out.append(c);
+ }
+ break;
+ }
+
+ }
+ out.append("\"");
+ }
+
+ private void newline() {
+ if (indent == null) {
+ return;
+ }
+
+ out.append("\n");
+ for (int i = 0; i < stack.size(); i++) {
+ out.append(indent);
+ }
+ }
+
+ /**
+ * Encodes the key (property name) to this stringer.
+ *
+ * @param name the name of the forthcoming value. May not be null.
+ * @return this stringer.
+ */
+ public JSONStringer key(String name) throws JSONException {
+ if (name == null) {
+ throw new JSONException("Names must be non-null");
+ }
+ beforeKey();
+ string(name);
+ return this;
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a name. Also
+ * adjusts the stack to expect the key's value.
+ */
+ private void beforeKey() throws JSONException {
+ Scope context = peek();
+ if (context == Scope.NONEMPTY_OBJECT) { // first in object
+ out.append(',');
+ } else if (context != Scope.EMPTY_OBJECT) { // not in an object!
+ throw new JSONException("Nesting problem");
+ }
+ newline();
+ replaceTop(Scope.DANGLING_KEY);
+ }
+
+ /**
+ * Inserts any necessary separators and whitespace before a literal value,
+ * inline array, or inline object. Also adjusts the stack to expect either a
+ * closing bracket or another element.
+ */
+ private void beforeValue() throws JSONException {
+ if (stack.isEmpty()) {
+ return;
+ }
+
+ Scope context = peek();
+ if (context == Scope.EMPTY_ARRAY) { // first in array
+ replaceTop(Scope.NONEMPTY_ARRAY);
+ newline();
+ } else if (context == Scope.NONEMPTY_ARRAY) { // another in array
+ out.append(',');
+ newline();
+ } else if (context == Scope.DANGLING_KEY) { // value for key
+ out.append(indent == null ? ":" : ": ");
+ replaceTop(Scope.NONEMPTY_OBJECT);
+ } else if (context != Scope.NULL) {
+ throw new JSONException("Nesting problem");
+ }
+ }
+
+ /**
+ * Returns the encoded JSON string.
+ *
+ * <p>If invoked with unterminated arrays or unclosed objects, this method's
+ * return value is undefined.
+ *
+ * <p><strong>Warning:</strong> although it contradicts the general contract
+ * of {@link Object#toString}, this method returns null if the stringer
+ * contains no data.
+ */
+ @Override public String toString() {
+ return out.length() == 0 ? null : out.toString();
+ }
+}
diff --git a/base/src/main/java/org/json/JSONTokener.java b/base/src/main/java/org/json/JSONTokener.java
new file mode 100644
index 000000000..4bdd9ad37
--- /dev/null
+++ b/base/src/main/java/org/json/JSONTokener.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * 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 org.json;
+
+
+// Note: this class was written without inspecting the non-free org.json sourcecode.
+
+/**
+ * Parses a JSON (<a href="http://www.ietf.org/rfc/rfc4627.txt">RFC 4627</a>)
+ * encoded string into the corresponding object. Most clients of
+ * this class will use only need the {@link #JSONTokener(String) constructor}
+ * and {@link #nextValue} method. Example usage: <pre>
+ * String json = "{"
+ * + " \"query\": \"Pizza\", "
+ * + " \"locations\": [ 94043, 90210 ] "
+ * + "}";
+ *
+ * JSONObject object = (JSONObject) new JSONTokener(json).nextValue();
+ * String query = object.getString("query");
+ * JSONArray locations = object.getJSONArray("locations");</pre>
+ *
+ * <p>For best interoperability and performance use JSON that complies with
+ * RFC 4627, such as that generated by {@link JSONStringer}. For legacy reasons
+ * this parser is lenient, so a successful parse does not indicate that the
+ * input string was valid JSON. All of the following syntax errors will be
+ * ignored:
+ * <ul>
+ * <li>End of line comments starting with {@code //} or {@code #} and ending
+ * with a newline character.
+ * <li>C-style comments starting with {@code /*} and ending with
+ * {@code *}{@code /}. Such comments may not be nested.
+ * <li>Strings that are unquoted or {@code 'single quoted'}.
+ * <li>Hexadecimal integers prefixed with {@code 0x} or {@code 0X}.
+ * <li>Octal integers prefixed with {@code 0}.
+ * <li>Array elements separated by {@code ;}.
+ * <li>Unnecessary array separators. These are interpreted as if null was the
+ * omitted value.
+ * <li>Key-value pairs separated by {@code =} or {@code =>}.
+ * <li>Key-value pairs separated by {@code ;}.
+ * </ul>
+ *
+ * <p>Each tokener may be used to parse a single JSON string. Instances of this
+ * class are not thread safe. Although this class is nonfinal, it was not
+ * designed for inheritance and should not be subclassed. In particular,
+ * self-use by overrideable methods is not specified. See <i>Effective Java</i>
+ * Item 17, "Design and Document or inheritance or else prohibit it" for further
+ * information.
+ */
+public class JSONTokener {
+
+ /** The input JSON. */
+ private final String in;
+
+ /**
+ * The index of the next character to be returned by {@link #next}. When
+ * the input is exhausted, this equals the input's length.
+ */
+ private int pos;
+
+ /**
+ * @param in JSON encoded string. Null is not permitted and will yield a
+ * tokener that throws {@code NullPointerExceptions} when methods are
+ * called.
+ */
+ public JSONTokener(String in) {
+ // consume an optional byte order mark (BOM) if it exists
+ if (in != null && in.startsWith("\ufeff")) {
+ in = in.substring(1);
+ }
+ this.in = in;
+ }
+
+ /**
+ * Returns the next value from the input.
+ *
+ * @return a {@link JSONObject}, {@link JSONArray}, String, Boolean,
+ * Integer, Long, Double or {@link JSONObject#NULL}.
+ * @throws JSONException if the input is malformed.
+ */
+ public Object nextValue() throws JSONException {
+ int c = nextCleanInternal();
+ switch (c) {
+ case -1:
+ throw syntaxError("End of input");
+
+ case '{':
+ return readObject();
+
+ case '[':
+ return readArray();
+
+ case '\'':
+ case '"':
+ return nextString((char) c);
+
+ default:
+ pos--;
+ return readLiteral();
+ }
+ }
+
+ private int nextCleanInternal() throws JSONException {
+ while (pos < in.length()) {
+ int c = in.charAt(pos++);
+ switch (c) {
+ case '\t':
+ case ' ':
+ case '\n':
+ case '\r':
+ continue;
+
+ case '/':
+ if (pos == in.length()) {
+ return c;
+ }
+
+ char peek = in.charAt(pos);
+ switch (peek) {
+ case '*':
+ // skip a /* c-style comment */
+ pos++;
+ int commentEnd = in.indexOf("*/", pos);
+ if (commentEnd == -1) {
+ throw syntaxError("Unterminated comment");
+ }
+ pos = commentEnd + 2;
+ continue;
+
+ case '/':
+ // skip a // end-of-line comment
+ pos++;
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+
+ case '#':
+ /*
+ * Skip a # hash end-of-line comment. The JSON RFC doesn't
+ * specify this behavior, but it's required to parse
+ * existing documents. See http://b/2571423.
+ */
+ skipToEndOfLine();
+ continue;
+
+ default:
+ return c;
+ }
+ }
+
+ return -1;
+ }
+
+ /**
+ * Advances the position until after the next newline character. If the line
+ * is terminated by "\r\n", the '\n' must be consumed as whitespace by the
+ * caller.
+ */
+ private void skipToEndOfLine() {
+ for (; pos < in.length(); pos++) {
+ char c = in.charAt(pos);
+ if (c == '\r' || c == '\n') {
+ pos++;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Returns the string up to but not including {@code quote}, unescaping any
+ * character escape sequences encountered along the way. The opening quote
+ * should have already been read. This consumes the closing quote, but does
+ * not include it in the returned string.
+ *
+ * @param quote either ' or ".
+ */
+ public String nextString(char quote) throws JSONException {
+ /*
+ * For strings that are free of escape sequences, we can just extract
+ * the result as a substring of the input. But if we encounter an escape
+ * sequence, we need to use a StringBuilder to compose the result.
+ */
+ StringBuilder builder = null;
+
+ /* the index of the first character not yet appended to the builder. */
+ int start = pos;
+
+ while (pos < in.length()) {
+ int c = in.charAt(pos++);
+ if (c == quote) {
+ if (builder == null) {
+ // a new string avoids leaking memory
+ return new String(in.substring(start, pos - 1));
+ } else {
+ builder.append(in, start, pos - 1);
+ return builder.toString();
+ }
+ }
+
+ if (c == '\\') {
+ if (pos == in.length()) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+ if (builder == null) {
+ builder = new StringBuilder();
+ }
+ builder.append(in, start, pos - 1);
+ builder.append(readEscapeCharacter());
+ start = pos;
+ }
+ }
+
+ throw syntaxError("Unterminated string");
+ }
+
+ /**
+ * Unescapes the character identified by the character or characters that
+ * immediately follow a backslash. The backslash '\' should have already
+ * been read. This supports both unicode escapes "u000A" and two-character
+ * escapes "\n".
+ */
+ private char readEscapeCharacter() throws JSONException {
+ char escaped = in.charAt(pos++);
+ switch (escaped) {
+ case 'u':
+ if (pos + 4 > in.length()) {
+ throw syntaxError("Unterminated escape sequence");
+ }
+ String hex = in.substring(pos, pos + 4);
+ pos += 4;
+ try {
+ return (char) Integer.parseInt(hex, 16);
+ } catch (NumberFormatException nfe) {
+ throw syntaxError("Invalid escape sequence: " + hex);
+ }
+
+ case 't':
+ return '\t';
+
+ case 'b':
+ return '\b';
+
+ case 'n':
+ return '\n';
+
+ case 'r':
+ return '\r';
+
+ case 'f':
+ return '\f';
+
+ case '\'':
+ case '"':
+ case '\\':
+ default:
+ return escaped;
+ }
+ }
+
+ /**
+ * Reads a null, boolean, numeric or unquoted string literal value. Numeric
+ * values will be returned as an Integer, Long, or Double, in that order of
+ * preference.
+ */
+ private Object readLiteral() throws JSONException {
+ String literal = nextToInternal("{}[]/\\:,=;# \t\f");
+
+ if (literal.length() == 0) {
+ throw syntaxError("Expected literal value");
+ } else if ("null".equalsIgnoreCase(literal)) {
+ return JSONObject.NULL;
+ } else if ("true".equalsIgnoreCase(literal)) {
+ return Boolean.TRUE;
+ } else if ("false".equalsIgnoreCase(literal)) {
+ return Boolean.FALSE;
+ }
+
+ /* try to parse as an integral type... */
+ if (literal.indexOf('.') == -1) {
+ int base = 10;
+ String number = literal;
+ if (number.startsWith("0x") || number.startsWith("0X")) {
+ number = number.substring(2);
+ base = 16;
+ } else if (number.startsWith("0") && number.length() > 1) {
+ number = number.substring(1);
+ base = 8;
+ }
+ try {
+ long longValue = Long.parseLong(number, base);
+ if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) {
+ return (int) longValue;
+ } else {
+ return longValue;
+ }
+ } catch (NumberFormatException e) {
+ /*
+ * This only happens for integral numbers greater than
+ * Long.MAX_VALUE, numbers in exponential form (5e-10) and
+ * unquoted strings. Fall through to try floating point.
+ */
+ }
+ }
+
+ /* ...next try to parse as a floating point... */
+ try {
+ return Double.valueOf(literal);
+ } catch (NumberFormatException ignored) {
+ }
+
+ /* ... finally give up. We have an unquoted string */
+ return new String(literal); // a new string avoids leaking memory
+ }
+
+ /**
+ * Returns the string up to but not including any of the given characters or
+ * a newline character. This does not consume the excluded character.
+ */
+ private String nextToInternal(String excluded) {
+ int start = pos;
+ for (; pos < in.length(); pos++) {
+ char c = in.charAt(pos);
+ if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) {
+ return in.substring(start, pos);
+ }
+ }
+ return in.substring(start);
+ }
+
+ /**
+ * Reads a sequence of key/value pairs and the trailing closing brace '}' of
+ * an object. The opening brace '{' should have already been read.
+ */
+ private JSONObject readObject() throws JSONException {
+ JSONObject result = new JSONObject();
+
+ /* Peek to see if this is the empty object. */
+ int first = nextCleanInternal();
+ if (first == '}') {
+ return result;
+ } else if (first != -1) {
+ pos--;
+ }
+
+ while (true) {
+ Object name = nextValue();
+ if (!(name instanceof String)) {
+ if (name == null) {
+ throw syntaxError("Names cannot be null");
+ } else {
+ throw syntaxError("Names must be strings, but " + name
+ + " is of type " + name.getClass().getName());
+ }
+ }
+
+ /*
+ * Expect the name/value separator to be either a colon ':', an
+ * equals sign '=', or an arrow "=>". The last two are bogus but we
+ * include them because that's what the original implementation did.
+ */
+ int separator = nextCleanInternal();
+ if (separator != ':' && separator != '=') {
+ throw syntaxError("Expected ':' after " + name);
+ }
+ if (pos < in.length() && in.charAt(pos) == '>') {
+ pos++;
+ }
+
+ result.put((String) name, nextValue());
+
+ switch (nextCleanInternal()) {
+ case '}':
+ return result;
+ case ';':
+ case ',':
+ continue;
+ default:
+ throw syntaxError("Unterminated object");
+ }
+ }
+ }
+
+ /**
+ * Reads a sequence of values and the trailing closing brace ']' of an
+ * array. The opening brace '[' should have already been read. Note that
+ * "[]" yields an empty array, but "[,]" returns a two-element array
+ * equivalent to "[null,null]".
+ */
+ private JSONArray readArray() throws JSONException {
+ JSONArray result = new JSONArray();
+
+ /* to cover input that ends with ",]". */
+ boolean hasTrailingSeparator = false;
+
+ while (true) {
+ switch (nextCleanInternal()) {
+ case -1:
+ throw syntaxError("Unterminated array");
+ case ']':
+ if (hasTrailingSeparator) {
+ result.put(null);
+ }
+ return result;
+ case ',':
+ case ';':
+ /* A separator without a value first means "null". */
+ result.put(null);
+ hasTrailingSeparator = true;
+ continue;
+ default:
+ pos--;
+ }
+
+ result.put(nextValue());
+
+ switch (nextCleanInternal()) {
+ case ']':
+ return result;
+ case ',':
+ case ';':
+ hasTrailingSeparator = true;
+ continue;
+ default:
+ throw syntaxError("Unterminated array");
+ }
+ }
+ }
+
+ /**
+ * Returns an exception containing the given message plus the current
+ * position and the entire input string.
+ */
+ public JSONException syntaxError(String message) {
+ return new JSONException(message + this);
+ }
+
+ /**
+ * Returns the current position and the entire input string.
+ */
+ @Override public String toString() {
+ // consistent with the original implementation
+ return " at character " + pos + " of " + in;
+ }
+
+ /*
+ * Legacy APIs.
+ *
+ * None of the methods below are on the critical path of parsing JSON
+ * documents. They exist only because they were exposed by the original
+ * implementation and may be used by some clients.
+ */
+
+ /**
+ * Returns true until the input has been exhausted.
+ */
+ public boolean more() {
+ return pos < in.length();
+ }
+
+ /**
+ * Returns the next available character, or the null character '\0' if all
+ * input has been exhausted. The return value of this method is ambiguous
+ * for JSON strings that contain the character '\0'.
+ */
+ public char next() {
+ return pos < in.length() ? in.charAt(pos++) : '\0';
+ }
+
+ /**
+ * Returns the next available character if it equals {@code c}. Otherwise an
+ * exception is thrown.
+ */
+ public char next(char c) throws JSONException {
+ char result = next();
+ if (result != c) {
+ throw syntaxError("Expected " + c + " but was " + result);
+ }
+ return result;
+ }
+
+ /**
+ * Returns the next character that is not whitespace and does not belong to
+ * a comment. If the input is exhausted before such a character can be
+ * found, the null character '\0' is returned. The return value of this
+ * method is ambiguous for JSON strings that contain the character '\0'.
+ */
+ public char nextClean() throws JSONException {
+ int nextCleanInt = nextCleanInternal();
+ return nextCleanInt == -1 ? '\0' : (char) nextCleanInt;
+ }
+
+ /**
+ * Returns the next {@code length} characters of the input.
+ *
+ * <p>The returned string shares its backing character array with this
+ * tokener's input string. If a reference to the returned string may be held
+ * indefinitely, you should use {@code new String(result)} to copy it first
+ * to avoid memory leaks.
+ *
+ * @throws JSONException if the remaining input is not long enough to
+ * satisfy this request.
+ */
+ public String next(int length) throws JSONException {
+ if (pos + length > in.length()) {
+ throw syntaxError(length + " is out of bounds");
+ }
+ String result = in.substring(pos, pos + length);
+ pos += length;
+ return result;
+ }
+
+ /**
+ * Returns the {@link String#trim trimmed} string holding the characters up
+ * to but not including the first of:
+ * <ul>
+ * <li>any character in {@code excluded}
+ * <li>a newline character '\n'
+ * <li>a carriage return '\r'
+ * </ul>
+ *
+ * <p>The returned string shares its backing character array with this
+ * tokener's input string. If a reference to the returned string may be held
+ * indefinitely, you should use {@code new String(result)} to copy it first
+ * to avoid memory leaks.
+ *
+ * @return a possibly-empty string
+ */
+ public String nextTo(String excluded) {
+ if (excluded == null) {
+ throw new NullPointerException("excluded == null");
+ }
+ return nextToInternal(excluded).trim();
+ }
+
+ /**
+ * Equivalent to {@code nextTo(String.valueOf(excluded))}.
+ */
+ public String nextTo(char excluded) {
+ return nextToInternal(String.valueOf(excluded)).trim();
+ }
+
+ /**
+ * Advances past all input up to and including the next occurrence of
+ * {@code thru}. If the remaining input doesn't contain {@code thru}, the
+ * input is exhausted.
+ */
+ public void skipPast(String thru) {
+ int thruStart = in.indexOf(thru, pos);
+ pos = thruStart == -1 ? in.length() : (thruStart + thru.length());
+ }
+
+ /**
+ * Advances past all input up to but not including the next occurrence of
+ * {@code to}. If the remaining input doesn't contain {@code to}, the input
+ * is unchanged.
+ */
+ public char skipTo(char to) {
+ int index = in.indexOf(to, pos);
+ if (index != -1) {
+ pos = index;
+ return to;
+ } else {
+ return '\0';
+ }
+ }
+
+ /**
+ * Unreads the most recent character of input. If no input characters have
+ * been read, the input is unchanged.
+ */
+ public void back() {
+ if (--pos == -1) {
+ pos = 0;
+ }
+ }
+
+ /**
+ * Returns the integer [0..15] value for the given hex character, or -1
+ * for non-hex input.
+ *
+ * @param hex a character in the ranges [0-9], [A-F] or [a-f]. Any other
+ * character will yield a -1 result.
+ */
+ public static int dehexchar(char hex) {
+ if (hex >= '0' && hex <= '9') {
+ return hex - '0';
+ } else if (hex >= 'A' && hex <= 'F') {
+ return hex - 'A' + 10;
+ } else if (hex >= 'a' && hex <= 'f') {
+ return hex - 'a' + 10;
+ } else {
+ return -1;
+ }
+ }
+}