diff options
author | Markos Rapitis <mrapitis@ford.com> | 2017-05-01 12:38:27 -0400 |
---|---|---|
committer | Markos Rapitis <mrapitis@ford.com> | 2017-05-01 12:38:27 -0400 |
commit | b7edfd36ad19855d2205e2e27c4f1a7c4c948cc4 (patch) | |
tree | 27754478d861105150280184fe57b96e56b797d7 | |
parent | ef7929077cab597b7158c43a3d53bf127f963eb1 (diff) | |
download | sdl_android-feature/issue_469_enhancements.tar.gz |
Removed need to utilize OutputStream during streaming. Fixed a race condition in onStreamDataAvailable. Added accessory methods to start and end service on demand. Added sdl service type listener allowing for better lifecycle management.feature/issue_469_enhancements
5 files changed, 247 insertions, 127 deletions
diff --git a/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java b/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java index f77f16aa9..3c44b38ef 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java +++ b/sdl_android/src/main/java/com/smartdevicelink/encoder/VirtualDisplayEncoder.java @@ -22,14 +22,16 @@ import android.view.View; import android.view.Window; import android.view.WindowManager; +import com.smartdevicelink.protocol.enums.SessionType; +import com.smartdevicelink.proxy.SdlProxyALM; +import com.smartdevicelink.proxy.SdlProxyBase; import com.smartdevicelink.proxy.rpc.OnTouchEvent; import com.smartdevicelink.proxy.rpc.ScreenParams; import com.smartdevicelink.proxy.rpc.TouchCoord; import com.smartdevicelink.proxy.rpc.TouchEvent; import com.smartdevicelink.proxy.rpc.enums.TouchType; +import com.smartdevicelink.streaming.StreamWriterThread; -import java.io.IOException; -import java.io.OutputStream; import java.lang.reflect.Constructor; import java.nio.ByteBuffer; import java.util.List; @@ -48,15 +50,17 @@ public class VirtualDisplayEncoder { private volatile VirtualDisplay virtualDisplay = null; private volatile SdlPresentation presentation = null; private Class<? extends SdlPresentation> presentationClass = null; - private VideoStreamWriterThread streamWriterThread = null; + private StreamWriterThread streamWriterThread = null; private Context mContext; - private OutputStream sdlOutStream = null; private Boolean initPassed = false; private Handler uiHandler = new Handler(Looper.getMainLooper()); private final static int REFRESH_RATE_MS = 100; private final static Object CLOSE_VID_SESSION_LOCK = new Object(); private final static Object START_DISP_LOCK = new Object(); private final static Object STREAMING_LOCK = new Object(); + private static Integer SLEEP_TIME = 5; //milliseconds + private static Integer MAX_SLEEP_DURATION = 500; //milliseconds + public class StreamingParameters { protected int displayDensity = DisplayMetrics.DENSITY_HIGH; @@ -141,29 +145,37 @@ public class VirtualDisplayEncoder { * @param screenParams * @throws Exception */ - public void init(Context context, OutputStream videoStream, Class<? extends SdlPresentation> presentationClass, ScreenParams screenParams) throws Exception { + public void init(Context context, SdlProxyALM sdlProxy, Class<? extends SdlPresentation> presentationClass) throws Exception { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { Log.e(TAG, "API level of 21 required for VirtualDisplayEncoder"); throw new Exception("API level of 21 required"); } + if (context == null || sdlProxy == null || presentationClass == null) { + Log.e(TAG, "init parameters cannot be null for VirtualDisplayEncoder"); + throw new Exception("init parameters cannot be null"); + } + mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mContext = context; + //need to check for null values + ScreenParams screenParams = sdlProxy.getDisplayCapabilities().getScreenParams(); + + //need to check image resolution for null if(screenParams.getImageResolution().getResolutionHeight() != null){ streamingParams.videoHeight = screenParams.getImageResolution().getResolutionHeight(); } + //need to check image resolution for null if(screenParams.getImageResolution().getResolutionWidth() != null){ streamingParams.videoWidth = screenParams.getImageResolution().getResolutionWidth(); } - sdlOutStream = videoStream; - this.presentationClass = presentationClass; - setupVideoStreamWriter(); + setupVideoStreamWriter(sdlProxy); initPassed = true; } @@ -240,20 +252,8 @@ public class VirtualDisplayEncoder { private void closeVideoSession() { synchronized (CLOSE_VID_SESSION_LOCK) { - if (sdlOutStream != null) { - - try { - sdlOutStream.close(); - } catch (IOException e) { - e.printStackTrace(); - } - - sdlOutStream = null; - - if (streamWriterThread != null) { - streamWriterThread.clearOutputStream(); - streamWriterThread.clearByteBuffer(); - } + if (streamWriterThread != null) { + streamWriterThread.clearByteBuffer(); } } } @@ -390,18 +390,18 @@ public class VirtualDisplayEncoder { } private void onStreamDataAvailable(byte[] data, int size) { - if (sdlOutStream != null) { - try { - if (streamWriterThread.getOutputStream() == null) { - streamWriterThread.setOutputStream(sdlOutStream); - } + try { + Integer totalSleep = 0; + while (streamWriterThread.getByteBuffer() != null) { + Thread.sleep(SLEEP_TIME); //try to sleep until the byte buffer is clear + totalSleep = totalSleep + SLEEP_TIME; + if (totalSleep >= MAX_SLEEP_DURATION) + break; + } - streamWriterThread.setByteBuffer(data, size); - } catch (Exception e) { + streamWriterThread.setByteBuffer(data, size); + } catch (Exception e) { e.printStackTrace(); - } - } else { - Log.e(TAG, "sdlOutStream is null"); } } @@ -539,10 +539,10 @@ public class VirtualDisplayEncoder { }); } - private void setupVideoStreamWriter() { + private void setupVideoStreamWriter(SdlProxyALM sdlProxy) { if (streamWriterThread == null) { // Setup VideoStreamWriterThread thread - streamWriterThread = new VideoStreamWriterThread(); + streamWriterThread = new StreamWriterThread(sdlProxy, SessionType.NAV); streamWriterThread.setName("VideoStreamWriter"); streamWriterThread.setPriority(Thread.MAX_PRIORITY); streamWriterThread.setDaemon(true); @@ -560,93 +560,8 @@ public class VirtualDisplayEncoder { } catch (Exception ex) { ex.printStackTrace(); } - - streamWriterThread.clearOutputStream(); streamWriterThread.clearByteBuffer(); } streamWriterThread = null; } - - private class VideoStreamWriterThread extends Thread { - private Boolean isHalted = false; - private byte[] buf = null; - private Integer size = 0; - private OutputStream os = null; - - public OutputStream getOutputStream() { - return os; - } - - public byte[] getByteBuffer() { - return buf; - } - - public void setOutputStream(OutputStream os) { - synchronized (STREAMING_LOCK) { - clearOutputStream(); - this.os = os; - } - } - - public void setByteBuffer(byte[] buf, Integer size) { - synchronized (STREAMING_LOCK) { - clearByteBuffer(); - this.buf = buf; - this.size = size; - } - } - - private void clearOutputStream() { - synchronized (STREAMING_LOCK) { - try { - if (os != null) { - os.close(); - } - } catch (Exception e) { - e.printStackTrace(); - } - os = null; - } - } - - private void clearByteBuffer() { - synchronized (STREAMING_LOCK) { - try { - if (buf != null) { - buf = null; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - private void writeToStream() { - synchronized (STREAMING_LOCK) { - if (buf == null || os == null) - return; - - try { - os.write(buf, 0, size); - clearByteBuffer(); - } catch (Exception ex) { - ex.printStackTrace(); - } - } - } - - public void run() { - while (!isHalted) { - writeToStream(); - } - } - - /** - * Method that marks thread as halted. - */ - public void halt() { - isHalted = true; - } - } - }
\ No newline at end of file diff --git a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java index 7678eaaa6..fecd20528 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java +++ b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java @@ -58,6 +58,7 @@ import com.smartdevicelink.proxy.callbacks.OnServiceNACKed; import com.smartdevicelink.proxy.interfaces.IProxyListenerALM;
import com.smartdevicelink.proxy.interfaces.IProxyListenerBase;
import com.smartdevicelink.proxy.interfaces.IPutFileResponseListener;
+import com.smartdevicelink.proxy.interfaces.ISdlServiceListener;
import com.smartdevicelink.proxy.rpc.*;
import com.smartdevicelink.proxy.rpc.enums.AppHMIType;
import com.smartdevicelink.proxy.rpc.enums.AudioStreamingState;
@@ -89,6 +90,7 @@ import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener;
import com.smartdevicelink.security.SdlSecurityBase;
import com.smartdevicelink.streaming.StreamRPCPacketizer;
+import com.smartdevicelink.streaming.StreamWriterThread;
import com.smartdevicelink.trace.SdlTrace;
import com.smartdevicelink.trace.TraceDeviceInfo;
import com.smartdevicelink.trace.enums.InterfaceActivityDirection;
@@ -106,7 +108,10 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase> private SdlSession sdlSession = null;
private proxyListenerType _proxyListener = null;
-
+
+ private ISdlServiceListener _sdlServiceListener = null;
+ private StreamWriterThread _videoStreamWriter = null;
+
protected Service _appService = null;
private String sPoliciesURL = ""; //for testing only
@@ -333,9 +338,9 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase> RPCProtectedServiceStarted();
}
} else if (sessionType.eq(SessionType.NAV)) {
- NavServiceStarted();
+ NavServiceStarted(isEncrypted);
} else if (sessionType.eq(SessionType.PCM)) {
- AudioServiceStarted();
+ AudioServiceStarted(isEncrypted);
} else if (sessionType.eq(SessionType.RPC)){
cycleProxy(SdlDisconnectedReason.RPC_SESSION_ENDED);
}
@@ -3495,7 +3500,29 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase> }
public ScheduledExecutorService createScheduler(){
return Executors.newSingleThreadScheduledExecutor();
- }
+ }
+
+ public void setSdlServiceListener(ISdlServiceListener sdlServiceListener) {
+ _sdlServiceListener = sdlServiceListener;
+ }
+
+ public void setVideoStreamWriter(StreamWriterThread videoStreamWriter) {
+ _videoStreamWriter = videoStreamWriter;
+ }
+
+ public void startSdlService(boolean isEncrypted, SessionType serviceType) {
+ if (sdlSession == null) return; //log an error here
+ if (_sdlServiceListener == null) return; //log an error here
+
+ sdlSession.startService(serviceType, sdlSession.getSessionId(), isEncrypted);
+ }
+
+ public void endSdlService(SessionType serviceType) {
+ if (sdlSession == null) return; //log an error here
+ if (_sdlServiceListener == null) return; //log an error here
+
+ sdlSession.endService(serviceType, sdlSession.getSessionId());
+ }
/**
*Opens the video service (serviceType 11) and subsequently streams raw H264 video from an InputStream provided by the app
@@ -3792,48 +3819,78 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase> sdlSession.drainEncoder(endOfStream);
}
- private void NavServiceStarted() {
+ private void NavServiceStarted(boolean isEncrypted) {
navServiceStartResponseReceived = true;
navServiceStartResponse = true;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceStarted(SessionType.NAV, isEncrypted);
+
+ if (_videoStreamWriter != null)
+ _videoStreamWriter.handleSdlSession(sdlSession);
}
private void NavServiceStartedNACK() {
navServiceStartResponseReceived = true;
navServiceStartResponse = false;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceStartedNacked(SessionType.NAV);
}
- private void AudioServiceStarted() {
+ private void AudioServiceStarted(boolean isEncrypted) {
pcmServiceStartResponseReceived = true;
pcmServiceStartResponse = true;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceStarted(SessionType.PCM, isEncrypted);
}
private void RPCProtectedServiceStarted() {
rpcProtectedResponseReceived = true;
rpcProtectedStartResponse = true;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceStarted(SessionType.RPC, true);
}
private void AudioServiceStartedNACK() {
pcmServiceStartResponseReceived = true;
pcmServiceStartResponse = false;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceStartedNacked(SessionType.PCM);
}
private void NavServiceEnded() {
navServiceEndResponseReceived = true;
navServiceEndResponse = true;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceEnded(SessionType.NAV);
}
private void NavServiceEndedNACK() {
navServiceEndResponseReceived = true;
navServiceEndResponse = false;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceEndedNacked(SessionType.NAV);
}
private void AudioServiceEnded() {
pcmServiceEndResponseReceived = true;
pcmServiceEndResponse = true;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceEnded(SessionType.PCM);
}
private void AudioServiceEndedNACK() {
pcmServiceEndResponseReceived = true;
pcmServiceEndResponse = false;
+
+ if (_sdlServiceListener != null)
+ _sdlServiceListener.onSdlServiceEndedNacked(SessionType.PCM);
}
public void setAppService(Service mService)
diff --git a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBuilder.java b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBuilder.java index 9e6921a61..fbc2b998c 100644 --- a/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBuilder.java +++ b/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBuilder.java @@ -5,6 +5,7 @@ import java.util.Vector; import com.smartdevicelink.exception.SdlException; import com.smartdevicelink.proxy.interfaces.IProxyListenerALM; +import com.smartdevicelink.proxy.interfaces.ISdlServiceListener; import com.smartdevicelink.proxy.rpc.SdlMsgVersion; import com.smartdevicelink.proxy.rpc.TTSChunk; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; @@ -39,8 +40,10 @@ public class SdlProxyBuilder private boolean preRegister; private String sAppResumeHash; private BaseTransportConfig mTransport; - private List<Class<? extends SdlSecurityBase>> sdlSecList; - + private List<Class<? extends SdlSecurityBase>> sdlSecList; + private ISdlServiceListener sdlServiceListener; + + public static class Builder { // Required parameters @@ -66,6 +69,7 @@ public class SdlProxyBuilder private String sAppResumeHash = null; private List<Class<? extends SdlSecurityBase>> sdlSecList = null; private BaseTransportConfig mTransport; //Initialized in constructor + private ISdlServiceListener sdlServiceListener = null; /** * @deprecated Use Builder(IProxyListenerALM, String, String, Boolean, Context) instead @@ -120,12 +124,15 @@ public class SdlProxyBuilder { mTransport = val; return this; } public Builder setSdlSecurity(List<Class<? extends SdlSecurityBase>> val) { sdlSecList = val; return this; } + public Builder setSdlServiceListener(ISdlServiceListener val) + { sdlServiceListener = val; return this; } public SdlProxyALM build() throws SdlException { SdlProxyBuilder obj = new SdlProxyBuilder(this); SdlProxyALM proxy = new SdlProxyALM(obj.service,obj.listener,obj.sdlProxyConfigurationResources,obj.appName,obj.ttsChunks,obj.sShortAppName,obj.vrSynonyms,obj.isMediaApp,obj.sdlMessageVersion,obj.lang,obj.hmiLang,obj.vrAppHMITypes,obj.appId,obj.autoActivateID,obj.callbackToUIThread,obj.preRegister,obj.sAppResumeHash,obj.mTransport); proxy.setSdlSecurityClassList(obj.sdlSecList); + proxy.setSdlServiceListener(obj.sdlServiceListener); return proxy; } } @@ -152,6 +159,7 @@ public class SdlProxyBuilder sAppResumeHash = builder.sAppResumeHash; mTransport = builder.mTransport; sdlSecList = builder.sdlSecList; + sdlServiceListener = builder.sdlServiceListener; } } diff --git a/sdl_android/src/main/java/com/smartdevicelink/proxy/interfaces/ISdlServiceListener.java b/sdl_android/src/main/java/com/smartdevicelink/proxy/interfaces/ISdlServiceListener.java new file mode 100644 index 000000000..2e9323a61 --- /dev/null +++ b/sdl_android/src/main/java/com/smartdevicelink/proxy/interfaces/ISdlServiceListener.java @@ -0,0 +1,10 @@ +package com.smartdevicelink.proxy.interfaces;
+
+import com.smartdevicelink.protocol.enums.SessionType;
+
+public interface ISdlServiceListener {
+ public void onSdlServiceStarted(SessionType type, boolean isEncrypted);
+ public void onSdlServiceStartedNacked(SessionType type);
+ public void onSdlServiceEnded(SessionType type);
+ public void onSdlServiceEndedNacked(SessionType type);
+}
diff --git a/sdl_android/src/main/java/com/smartdevicelink/streaming/StreamWriterThread.java b/sdl_android/src/main/java/com/smartdevicelink/streaming/StreamWriterThread.java new file mode 100644 index 000000000..84579e395 --- /dev/null +++ b/sdl_android/src/main/java/com/smartdevicelink/streaming/StreamWriterThread.java @@ -0,0 +1,130 @@ +package com.smartdevicelink.streaming;
+
+import com.smartdevicelink.SdlConnection.SdlSession;
+import com.smartdevicelink.protocol.ProtocolMessage;
+import com.smartdevicelink.protocol.enums.SessionType;
+import com.smartdevicelink.proxy.SdlProxyALM;
+
+public class StreamWriterThread extends Thread {
+ private Boolean isHalted = false;
+ private byte[] buf = null;
+ private Integer size = 0;
+ private final static Object lock = new Object();
+ private SessionType serviceType;
+ private SdlSession sdlSession;
+
+ private boolean isServiceProtected;
+ 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 ENC_READ_SIZE = TLS_MAX_RECORD_SIZE - TLS_RECORD_HEADER_SIZE - TLS_RECORD_MES_AUTH_CDE_SIZE - TLS_MAX_RECORD_PADDING_SIZE;
+
+
+ public StreamWriterThread(SdlProxyALM sdlProxy, SessionType serviceType) {
+ if (sdlProxy == null){
+ return;
+ }
+ sdlProxy.setVideoStreamWriter(this);
+ this.serviceType = serviceType;
+ }
+
+ public void handleSdlSession(SdlSession sdlSession){
+ if (sdlSession == null){
+ return;
+ }
+ this.sdlSession = sdlSession;
+ isServiceProtected = sdlSession.isServiceProtected(serviceType);
+ }
+
+ public byte[] getByteBuffer() {
+ synchronized (lock) {
+ return buf;
+ }
+ }
+
+ public void setByteBuffer(byte[] buf, Integer size) {
+ synchronized (lock) {
+ this.buf = buf;
+ this.size = size;
+ }
+ }
+
+ public void clearByteBuffer() {
+ synchronized (lock) {
+ try {
+ if (buf != null) {
+ buf = null;
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private ProtocolMessage createStreamPacket(byte[] byteData) {
+ ProtocolMessage pm = new ProtocolMessage();
+ pm.setSessionID(sdlSession.getSessionId());
+ pm.setSessionType(serviceType);
+ pm.setFunctionID(0);
+ pm.setCorrID(0);
+ pm.setData(byteData, byteData.length);
+ pm.setPayloadProtected(isServiceProtected);
+ return pm;
+ }
+
+ public static byte[][] divideArray(byte[] source, int chunksize) {
+ byte[][] ret = new byte[(int)Math.ceil(source.length / (double)chunksize)][chunksize];
+
+ int start = 0;
+
+ for(int i = 0; i < ret.length; i++) {
+ if(start + chunksize > source.length) {
+ System.arraycopy(source, start, ret[i], 0, source.length - start);
+ } else {
+ System.arraycopy(source, start, ret[i], 0, chunksize);
+ }
+ start += chunksize ;
+ }
+
+ return ret;
+ }
+
+ private void writeToStream() {
+ synchronized (lock) {
+ if (buf == null || sdlSession == null) return;
+
+ try {
+ if (isServiceProtected && buf.length > ENC_READ_SIZE){
+ byte[][] ret = divideArray(buf, ENC_READ_SIZE);
+ for(int i = 0; i < ret.length; i++) {
+ byte[] byteData = ret[i];
+ ProtocolMessage pm = createStreamPacket(byteData);
+ sdlSession.sendStreamPacket(pm);
+ }
+ }
+ else {
+ ProtocolMessage pm = createStreamPacket(buf);
+ sdlSession.sendStreamPacket(pm);
+ }
+
+ clearByteBuffer();
+ } catch (Exception ex) {
+ ex.printStackTrace();
+ }
+ }
+ }
+
+ public void run() {
+ while (!isHalted) {
+ writeToStream();
+ }
+ }
+
+ /**
+ * Method that marks thread as halted.
+ */
+ public void halt() {
+ isHalted = true;
+ }
+}
|