diff options
author | RHenigan <heniganr1@gmail.com> | 2020-06-12 16:45:23 -0400 |
---|---|---|
committer | RHenigan <heniganr1@gmail.com> | 2020-06-12 16:45:23 -0400 |
commit | 7a709dc35681f5cc5f5679cc83aa69f71158ebb3 (patch) | |
tree | a91c37433c748b72d83031991e0c3b4b7d5d0903 | |
parent | 8b943aeaab90ea0af7767bfad8562a5f729af065 (diff) | |
parent | 6b52e38eb9cb29e4c28e98191c312d2c1fd82607 (diff) | |
download | sdl_android-bugfix/issue_1369.tar.gz |
Merge branch 'develop' into bugfix/issue_1369bugfix/issue_1369
18 files changed, 3132 insertions, 3491 deletions
diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 056e51be8..c2ec6bf32 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -25,7 +25,7 @@ jobs: script: ./android/gradlew -p ./android :sdl_android:connectedCheck - name: Hello Sdl Android Tests - run: ./android/gradlew -p ./android :hello_sdl_android:build + run: ./android/gradlew -p ./android/hello_sdl_android test - name: Sdl JavaSE Tests run: ./javaSE/gradlew -p ./javaSE test diff --git a/android/hello_sdl_android/build.gradle b/android/hello_sdl_android/build.gradle index ac8b92278..da649cbbb 100755 --- a/android/hello_sdl_android/build.gradle +++ b/android/hello_sdl_android/build.gradle @@ -1,7 +1,7 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 28 + compileSdkVersion 29 defaultConfig { applicationId "com.sdl.hellosdlandroid" minSdkVersion 14 diff --git a/android/hello_sdl_android/src/main/AndroidManifest.xml b/android/hello_sdl_android/src/main/AndroidManifest.xml index 72a191542..214a04355 100755 --- a/android/hello_sdl_android/src/main/AndroidManifest.xml +++ b/android/hello_sdl_android/src/main/AndroidManifest.xml @@ -39,12 +39,15 @@ android:resource="@xml/accessory_filter" /> </activity> - <service android:name="com.sdl.hellosdlandroid.SdlService" > + <service + android:name="com.sdl.hellosdlandroid.SdlService" + android:foregroundServiceType="connectedDevice"> </service> <service android:name=".SdlRouterService" android:exported="true" - android:process="com.smartdevicelink.router"> + android:process="com.smartdevicelink.router" + android:foregroundServiceType="connectedDevice"> <intent-filter> <action android:name="com.smartdevicelink.router.service"/> </intent-filter> diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java index 589af4c52..84865f21e 100644 --- a/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java +++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/managers/SdlManagerTests.java @@ -3,14 +3,13 @@ package com.smartdevicelink.managers; import android.content.Context; import com.smartdevicelink.AndroidTestCase2; -import com.smartdevicelink.exception.SdlException; import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate; import com.smartdevicelink.managers.lockscreen.LockScreenConfig; import com.smartdevicelink.protocol.enums.FunctionID; import com.smartdevicelink.proxy.RPCMessage; import com.smartdevicelink.proxy.RPCRequest; import com.smartdevicelink.proxy.RPCResponse; -import com.smartdevicelink.proxy.SdlProxyBase; +import com.smartdevicelink.proxy.interfaces.ISdl; import com.smartdevicelink.proxy.rpc.GetAppServiceDataResponse; import com.smartdevicelink.proxy.rpc.GetVehicleData; import com.smartdevicelink.proxy.rpc.OnAppServiceData; @@ -49,7 +48,7 @@ public class SdlManagerTests extends AndroidTestCase2 { private TemplateColorScheme templateColorScheme; private int listenerCalledCounter; private SdlManager sdlManager; - private SdlProxyBase sdlProxyBase; + private ISdl internalInterface; // transport related @SuppressWarnings("FieldCanBeLocal") @@ -136,9 +135,9 @@ public class SdlManagerTests extends AndroidTestCase2 { builder.setContext(mTestContext); manager = builder.build(); - // mock SdlProxyBase and set it manually - sdlProxyBase = mock(SdlProxyBase.class); - manager.setProxy(sdlProxyBase); + // mock internalInterface and set it manually + internalInterface = mock(ISdl.class); + manager._internalInterface = internalInterface; return manager; } @@ -184,8 +183,11 @@ public class SdlManagerTests extends AndroidTestCase2 { public void testStartingManager(){ listenerCalledCounter = 0; - - sdlManager.start(); + + try { + sdlManager.start(); + } catch (Exception e) { + } // Create and force all sub managers to be ready manually. Because SdlManager will not start until all sub managers are ready. // Note: SdlManager.initialize() will not be called automatically by proxy as in real life because we have mock proxy not a real one @@ -198,7 +200,7 @@ public class SdlManagerTests extends AndroidTestCase2 { sdlManager.getLockScreenManager().transitionToState(BaseSubManager.READY); // Make sure the listener is called exactly once - assertEquals("Listener was not called or called more/less frequently than expected", listenerCalledCounter, 1); + assertEquals("Listener was not called or called more/less frequently than expected", 1, listenerCalledCounter); } public void testManagerStates() { @@ -314,7 +316,7 @@ public class SdlManagerTests extends AndroidTestCase2 { public void testSendRPC(){ listenerCalledCounter = 0; - // When sdlProxyBase.sendRPCRequest() is called, create a fake success response + // When internalInterface.sendRPC() is called, create a fake success response Answer<Void> answer = new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) { @@ -326,11 +328,7 @@ public class SdlManagerTests extends AndroidTestCase2 { return null; } }; - try { - doAnswer(answer).when(sdlProxyBase).sendRPC(any(RPCMessage.class)); - } catch (SdlException e) { - e.printStackTrace(); - } + doAnswer(answer).when(internalInterface).sendRPC(any(RPCMessage.class)); // Test send RPC request @@ -347,7 +345,7 @@ public class SdlManagerTests extends AndroidTestCase2 { sdlManager.sendRPC(request); // Make sure the listener is called exactly once - assertEquals("Listener was not called or called more/less frequently than expected", listenerCalledCounter, 1); + assertEquals("Listener was not called or called more/less frequently than expected", 1, listenerCalledCounter); } public void testSendRPCs(){ @@ -358,10 +356,10 @@ public class SdlManagerTests extends AndroidTestCase2 { testSendMultipleRPCs(true); } - private void testSendMultipleRPCs(boolean sequentialSend){ + private void testSendMultipleRPCs(boolean sequentialSend) { listenerCalledCounter = 0; - // When sdlProxyBase.sendRPCRequests() is called, call listener.onFinished() to fake the response + // When internalInterface.sendRPCs() is called, call listener.onFinished() to fake the response final Answer<Void> answer = new Answer<Void>() { @Override public Void answer(InvocationOnMock invocation) { @@ -371,15 +369,12 @@ public class SdlManagerTests extends AndroidTestCase2 { return null; } }; - try { - if (sequentialSend){ - doAnswer(answer).when(sdlProxyBase).sendSequentialRequests(any(List.class), any(OnMultipleRequestListener.class)); - } else { - doAnswer(answer).when(sdlProxyBase).sendRequests(any(List.class), any(OnMultipleRequestListener.class)); - } - } catch (SdlException e) { - e.printStackTrace(); + if (sequentialSend) { + doAnswer(answer).when(internalInterface).sendSequentialRPCs(any(List.class), any(OnMultipleRequestListener.class)); + + } else { + doAnswer(answer).when(internalInterface).sendRPCs(any(List.class), any(OnMultipleRequestListener.class)); } @@ -387,7 +382,8 @@ public class SdlManagerTests extends AndroidTestCase2 { List<RPCMessage> rpcsList = Arrays.asList(new GetVehicleData(), new Show(), new OnAppServiceData(), new GetAppServiceDataResponse()); OnMultipleRequestListener onMultipleRequestListener = new OnMultipleRequestListener() { @Override - public void onUpdate(int remainingRequests) { } + public void onUpdate(int remainingRequests) { + } @Override public void onFinished() { @@ -395,10 +391,12 @@ public class SdlManagerTests extends AndroidTestCase2 { } @Override - public void onError(int correlationId, Result resultCode, String info) {} + public void onError(int correlationId, Result resultCode, String info) { + } @Override - public void onResponse(int correlationId, RPCResponse response) {} + public void onResponse(int correlationId, RPCResponse response) { + } }; if (sequentialSend) { sdlManager.sendSequentialRPCs(rpcsList, onMultipleRequestListener); @@ -408,7 +406,6 @@ public class SdlManagerTests extends AndroidTestCase2 { // Make sure the listener is called exactly once - assertEquals("Listener was not called or called more/less frequently than expected", listenerCalledCounter, 1); + assertEquals("Listener was not called or called more/less frequently than expected", 1, listenerCalledCounter); } - } diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java index bc8bd7194..2a34dc62e 100644 --- a/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java @@ -41,1225 +41,352 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.util.Log; -import com.smartdevicelink.exception.SdlException; import com.smartdevicelink.managers.audio.AudioStreamManager; import com.smartdevicelink.managers.file.FileManager; -import com.smartdevicelink.managers.file.FileManagerConfig; -import com.smartdevicelink.managers.file.filetypes.SdlArtwork; -import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate; import com.smartdevicelink.managers.lockscreen.LockScreenConfig; import com.smartdevicelink.managers.lockscreen.LockScreenManager; import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.managers.screen.ScreenManager; import com.smartdevicelink.managers.video.VideoStreamManager; -import com.smartdevicelink.protocol.enums.FunctionID; -import com.smartdevicelink.protocol.enums.SessionType; -import com.smartdevicelink.proxy.RPCMessage; -import com.smartdevicelink.proxy.RPCRequest; -import com.smartdevicelink.proxy.RPCResponse; -import com.smartdevicelink.proxy.SdlProxyBase; -import com.smartdevicelink.proxy.SystemCapabilityManager; -import com.smartdevicelink.proxy.callbacks.OnServiceEnded; -import com.smartdevicelink.proxy.callbacks.OnServiceNACKed; -import com.smartdevicelink.proxy.interfaces.IAudioStreamListener; -import com.smartdevicelink.proxy.interfaces.ISdl; -import com.smartdevicelink.proxy.interfaces.ISdlServiceListener; -import com.smartdevicelink.proxy.interfaces.IVideoStreamListener; -import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener; -import com.smartdevicelink.proxy.rpc.ChangeRegistration; -import com.smartdevicelink.proxy.rpc.OnHMIStatus; -import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; -import com.smartdevicelink.proxy.rpc.SdlMsgVersion; -import com.smartdevicelink.proxy.rpc.SetAppIcon; -import com.smartdevicelink.proxy.rpc.TTSChunk; -import com.smartdevicelink.proxy.rpc.TemplateColorScheme; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; -import com.smartdevicelink.proxy.rpc.enums.Language; -import com.smartdevicelink.proxy.rpc.enums.Result; import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason; -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.proxy.rpc.listeners.OnRPCRequestListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener; -import com.smartdevicelink.security.SdlSecurityBase; -import com.smartdevicelink.streaming.audio.AudioStreamingCodec; -import com.smartdevicelink.streaming.audio.AudioStreamingParams; -import com.smartdevicelink.streaming.video.VideoStreamingParameters; import com.smartdevicelink.transport.BaseTransportConfig; import com.smartdevicelink.transport.MultiplexTransportConfig; import com.smartdevicelink.transport.enums.TransportType; import com.smartdevicelink.transport.utl.TransportRecord; import com.smartdevicelink.util.DebugTool; -import com.smartdevicelink.util.Version; -import org.json.JSONException; - -import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Vector; /** * <strong>SDLManager</strong> <br> - * + * <p> * This is the main point of contact between an application and SDL <br> - * + * <p> * It is broken down to these areas: <br> - * + * <p> * 1. SDLManagerBuilder <br> * 2. ISdl Interface along with its overridden methods - This can be passed into attached managers <br> * 3. Sending Requests <br> * 4. Helper methods */ -public class SdlManager extends BaseSdlManager{ - private static final String TAG = "SdlManager"; - private SdlProxyBase proxy; - private SdlArtwork appIcon; - private Context context; - private SdlManagerListener managerListener; - private List<Class<? extends SdlSecurityBase>> sdlSecList; - private LockScreenConfig lockScreenConfig; - private FileManagerConfig fileManagerConfig; - private ServiceEncryptionListener serviceEncryptionListener; - - // Managers - private PermissionManager permissionManager; - private FileManager fileManager; - private LockScreenManager lockScreenManager; - private ScreenManager screenManager; - private VideoStreamManager videoStreamManager; - private AudioStreamManager audioStreamManager; - - - // Initialize proxyBridge with anonymous lifecycleListener - private final ProxyBridge proxyBridge = new ProxyBridge(new ProxyBridge.LifecycleListener() { - @Override - public void onProxyConnected() { - DebugTool.logInfo("Proxy is connected. Now initializing."); - changeRegistrationRetry = 0; - checkLifecycleConfiguration(); - initialize(); - } - - @Override - public void onProxyClosed(String info, Exception e, SdlDisconnectedReason reason){ - if (!reason.equals(SdlDisconnectedReason.LANGUAGE_CHANGE)){ - dispose(); - } - } - - @Override - public void onServiceEnded(OnServiceEnded serviceEnded){ - - } - - @Override - public void onServiceNACKed(OnServiceNACKed serviceNACKed){ - - } - - @Override - public void onError(String info, Exception e){ - - } - }); - - // Sub manager listener - private final CompletionListener subManagerListener = new CompletionListener() { - @Override - public synchronized void onComplete(boolean success) { - if(!success){ - Log.e(TAG, "Sub manager failed to initialize"); - } - checkState(); - } - }; - - @Override - void checkState() { - if (permissionManager != null && fileManager != null && screenManager != null && (!lockScreenConfig.isEnabled() || lockScreenManager != null)) { - if (permissionManager.getState() == BaseSubManager.READY && fileManager.getState() == BaseSubManager.READY && screenManager.getState() == BaseSubManager.READY && (!lockScreenConfig.isEnabled() || lockScreenManager.getState() == BaseSubManager.READY)) { - DebugTool.logInfo("Starting sdl manager, all sub managers are in ready state"); - transitionToState(BaseSubManager.READY); - handleQueuedNotifications(); - notifyDevListener(null); - onReady(); - } else if (permissionManager.getState() == BaseSubManager.ERROR && fileManager.getState() == BaseSubManager.ERROR && screenManager.getState() == BaseSubManager.ERROR && (!lockScreenConfig.isEnabled() || lockScreenManager.getState() == BaseSubManager.ERROR)) { - String info = "ERROR starting sdl manager, all sub managers are in error state"; - Log.e(TAG, info); - transitionToState(BaseSubManager.ERROR); - notifyDevListener(info); - } else if (permissionManager.getState() == BaseSubManager.SETTING_UP || fileManager.getState() == BaseSubManager.SETTING_UP || screenManager.getState() == BaseSubManager.SETTING_UP || (lockScreenConfig.isEnabled() && lockScreenManager != null && lockScreenManager.getState() == BaseSubManager.SETTING_UP)) { - DebugTool.logInfo("SETTING UP sdl manager, some sub managers are still setting up"); - transitionToState(BaseSubManager.SETTING_UP); - // No need to notify developer here! - } else { - Log.w(TAG, "LIMITED starting sdl manager, some sub managers are in error or limited state and the others finished setting up"); - transitionToState(BaseSubManager.LIMITED); - handleQueuedNotifications(); - notifyDevListener(null); - onReady(); - } - } else { - // We should never be here, but somehow one of the sub-sub managers is null - String info = "ERROR one of the sdl sub managers is null"; - Log.e(TAG, info); - transitionToState(BaseSubManager.ERROR); - notifyDevListener(info); - } - } - - private void notifyDevListener(String info) { - if (managerListener != null) { - if (getState() == BaseSubManager.ERROR){ - managerListener.onError(info, null); - } else { - managerListener.onStart(); - } - } - } - - private void onReady(){ - // Set the app icon - if (SdlManager.this.appIcon != null && SdlManager.this.appIcon.getName() != null) { - if (fileManager != null && fileManager.getState() == BaseSubManager.READY && !fileManager.hasUploadedFile(SdlManager.this.appIcon)) { - fileManager.uploadArtwork(SdlManager.this.appIcon, new CompletionListener() { - @Override - public void onComplete(boolean success) { - if (success) { - SetAppIcon msg = new SetAppIcon(SdlManager.this.appIcon.getName()); - _internalInterface.sendRPCRequest(msg); - } - } - }); - } else { - SetAppIcon msg = new SetAppIcon(SdlManager.this.appIcon.getName()); - _internalInterface.sendRPCRequest(msg); - } - } - } +public class SdlManager extends BaseSdlManager { + private Context context; + private LockScreenConfig lockScreenConfig; - @Override - protected void checkLifecycleConfiguration(){ - final Language actualLanguage = this.getRegisterAppInterfaceResponse().getLanguage(); - final Language actualHMILanguage = this.getRegisterAppInterfaceResponse().getHmiDisplayLanguage(); + // Managers + private LockScreenManager lockScreenManager; + private VideoStreamManager videoStreamManager; + private AudioStreamManager audioStreamManager; - if ((actualLanguage != null && !actualLanguage.equals(language)) || (actualHMILanguage != null && !actualHMILanguage.equals(hmiLanguage))) { - - LifecycleConfigurationUpdate lcuNew = managerListener.managerShouldUpdateLifecycle(actualLanguage, actualHMILanguage); - LifecycleConfigurationUpdate lcuOld = managerListener.managerShouldUpdateLifecycle(actualLanguage); - final LifecycleConfigurationUpdate lcu; - ChangeRegistration changeRegistration; - if (lcuNew == null) { - lcu = lcuOld; - changeRegistration = new ChangeRegistration(actualLanguage, actualLanguage); - } else { - lcu = lcuNew; - changeRegistration = new ChangeRegistration(actualLanguage, actualHMILanguage); - } - - if (lcu != null) { - changeRegistration.setAppName(lcu.getAppName()); - changeRegistration.setNgnMediaScreenAppName(lcu.getShortAppName()); - changeRegistration.setTtsName(lcu.getTtsName()); - changeRegistration.setVrSynonyms(lcu.getVoiceRecognitionCommandNames()); - changeRegistration.setOnRPCResponseListener(new OnRPCResponseListener() { - @Override - public void onResponse(int correlationId, RPCResponse response) { - if (response.getSuccess()){ - // go through and change sdlManager properties that were changed via the LCU update - hmiLanguage = actualHMILanguage; - language = actualLanguage; - - if (lcu.getAppName() != null) { - appName = lcu.getAppName(); - } - - if (lcu.getShortAppName() != null) { - shortAppName = lcu.getShortAppName(); - } - - if (lcu.getTtsName() != null) { - ttsChunks = lcu.getTtsName(); - } - - if (lcu.getVoiceRecognitionCommandNames() != null) { - vrSynonyms = lcu.getVoiceRecognitionCommandNames(); - } - } - try { - DebugTool.logInfo(response.serializeJSON().toString()); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - @Override - public void onError(int correlationId, Result resultCode, String info) { - DebugTool.logError("Change Registration onError: " + resultCode + " | Info: " + info); - changeRegistrationRetry++; - if (changeRegistrationRetry < MAX_RETRY) { - final Handler handler = new Handler(Looper.getMainLooper()); - handler.postDelayed(new Runnable() { - @Override - public void run() { - checkLifecycleConfiguration(); - DebugTool.logInfo("Retry Change Registration Count: " + changeRegistrationRetry); - } - }, 3000); - } - } - }); - this.sendRPC(changeRegistration); - } - } - } - - @Override - protected void initialize(){ - // Instantiate sub managers - this.permissionManager = new PermissionManager(_internalInterface); - this.fileManager = new FileManager(_internalInterface, context, fileManagerConfig); - if (lockScreenConfig.isEnabled()) { - this.lockScreenManager = new LockScreenManager(lockScreenConfig, context, _internalInterface); - } - this.screenManager = new ScreenManager(_internalInterface, this.fileManager); - if(getAppTypes().contains(AppHMIType.NAVIGATION) || getAppTypes().contains(AppHMIType.PROJECTION)){ - this.videoStreamManager = new VideoStreamManager(_internalInterface); - } else { - this.videoStreamManager = null; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN - && (getAppTypes().contains(AppHMIType.NAVIGATION) || getAppTypes().contains(AppHMIType.PROJECTION)) ) { - this.audioStreamManager = new AudioStreamManager(_internalInterface, context); - } else { - this.audioStreamManager = null; - } - - // Start sub managers - this.permissionManager.start(subManagerListener); - this.fileManager.start(subManagerListener); - if (lockScreenConfig.isEnabled()){ - this.lockScreenManager.start(subManagerListener); - } - this.screenManager.start(subManagerListener); - } - - /** Dispose SdlManager and clean its resources - * <strong>Note: new instance of SdlManager should be created on every connection. SdlManager cannot be reused after getting disposed.</strong> - */ - @SuppressLint("NewApi") - @Override - public void dispose() { - if (this.permissionManager != null) { - this.permissionManager.dispose(); - } - - if (this.fileManager != null) { - this.fileManager.dispose(); - } + /** + * Starts up a SdlManager, and calls provided callback called once all BaseSubManagers are done setting up + */ + @Override + public void start() { + if (lifecycleManager == null) { + if (transport != null && transport.getTransportType() == TransportType.MULTIPLEX) { + //Do the thing + MultiplexTransportConfig multiplexTransportConfig = (MultiplexTransportConfig) (transport); + final MultiplexTransportConfig.TransportListener devListener = multiplexTransportConfig.getTransportListener(); + multiplexTransportConfig.setTransportListener(new MultiplexTransportConfig.TransportListener() { + @Override + public void onTransportEvent(List<TransportRecord> connectedTransports, boolean audioStreamTransportAvail, boolean videoStreamTransportAvail) { + + //Pass to submanagers that need it + if (videoStreamManager != null) { + videoStreamManager.handleTransportUpdated(connectedTransports, audioStreamTransportAvail, videoStreamTransportAvail); + } + + if (audioStreamManager != null) { + audioStreamManager.handleTransportUpdated(connectedTransports, audioStreamTransportAvail, videoStreamTransportAvail); + } + //If the developer supplied a listener to start, it is time to call that + if (devListener != null) { + devListener.onTransportEvent(connectedTransports, audioStreamTransportAvail, videoStreamTransportAvail); + } + } + }); + + //If the requires audio support has not been set, it should be set to true if the + //app is a media app, and false otherwise + if (multiplexTransportConfig.requiresAudioSupport() == null) { + multiplexTransportConfig.setRequiresAudioSupport(isMediaApp); + } + } + + super.start(); + + lifecycleManager.setContext(context); + lifecycleManager.start(); + } + } + + @Override + protected void initialize() { + // Instantiate sub managers + this.permissionManager = new PermissionManager(_internalInterface); + this.fileManager = new FileManager(_internalInterface, context, fileManagerConfig); + if (lockScreenConfig.isEnabled()) { + this.lockScreenManager = new LockScreenManager(lockScreenConfig, context, _internalInterface); + } + this.screenManager = new ScreenManager(_internalInterface, this.fileManager); + if (getAppTypes().contains(AppHMIType.NAVIGATION) || getAppTypes().contains(AppHMIType.PROJECTION)) { + this.videoStreamManager = new VideoStreamManager(_internalInterface); + } else { + this.videoStreamManager = null; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN + && (getAppTypes().contains(AppHMIType.NAVIGATION) || getAppTypes().contains(AppHMIType.PROJECTION))) { + this.audioStreamManager = new AudioStreamManager(_internalInterface, context); + } else { + this.audioStreamManager = null; + } + + // Start sub managers + this.permissionManager.start(subManagerListener); + this.fileManager.start(subManagerListener); + if (lockScreenConfig.isEnabled()) { + this.lockScreenManager.start(subManagerListener); + } + this.screenManager.start(subManagerListener); + } + + @Override + void checkState() { + if (permissionManager != null && fileManager != null && screenManager != null && (!lockScreenConfig.isEnabled() || lockScreenManager != null)) { + if (permissionManager.getState() == BaseSubManager.READY && fileManager.getState() == BaseSubManager.READY && screenManager.getState() == BaseSubManager.READY && (!lockScreenConfig.isEnabled() || lockScreenManager.getState() == BaseSubManager.READY)) { + DebugTool.logInfo("Starting sdl manager, all sub managers are in ready state"); + transitionToState(BaseSubManager.READY); + handleQueuedNotifications(); + notifyDevListener(null); + onReady(); + } else if (permissionManager.getState() == BaseSubManager.ERROR && fileManager.getState() == BaseSubManager.ERROR && screenManager.getState() == BaseSubManager.ERROR && (!lockScreenConfig.isEnabled() || lockScreenManager.getState() == BaseSubManager.ERROR)) { + String info = "ERROR starting sdl manager, all sub managers are in error state"; + Log.e(TAG, info); + transitionToState(BaseSubManager.ERROR); + notifyDevListener(info); + } else if (permissionManager.getState() == BaseSubManager.SETTING_UP || fileManager.getState() == BaseSubManager.SETTING_UP || screenManager.getState() == BaseSubManager.SETTING_UP || (lockScreenConfig.isEnabled() && lockScreenManager != null && lockScreenManager.getState() == BaseSubManager.SETTING_UP)) { + DebugTool.logInfo("SETTING UP sdl manager, some sub managers are still setting up"); + transitionToState(BaseSubManager.SETTING_UP); + // No need to notify developer here! + } else { + Log.w(TAG, "LIMITED starting sdl manager, some sub managers are in error or limited state and the others finished setting up"); + transitionToState(BaseSubManager.LIMITED); + handleQueuedNotifications(); + notifyDevListener(null); + onReady(); + } + } else { + // We should never be here, but somehow one of the sub-sub managers is null + String info = "ERROR one of the sdl sub managers is null"; + Log.e(TAG, info); + transitionToState(BaseSubManager.ERROR); + notifyDevListener(info); + } + } + + private void notifyDevListener(String info) { + if (managerListener != null) { + if (getState() == BaseSubManager.ERROR) { + managerListener.onError(info, null); + } else { + managerListener.onStart(); + } + } + } + + @Override + void retryChangeRegistration() { + changeRegistrationRetry++; + if (changeRegistrationRetry < MAX_RETRY) { + final Handler handler = new Handler(Looper.getMainLooper()); + handler.postDelayed(new Runnable() { + @Override + public void run() { + checkLifecycleConfiguration(); + DebugTool.logInfo("Retry Change Registration Count: " + changeRegistrationRetry); + } + }, 3000); + } + } + + @Override + void onProxyClosed(SdlDisconnectedReason reason) { + Log.i(TAG, "Proxy is closed."); + if (managerListener != null) { + managerListener.onDestroy(); + } + + if (reason == null || !reason.equals(SdlDisconnectedReason.LANGUAGE_CHANGE)) { + dispose(); + } + } - if (this.lockScreenManager != null) { - this.lockScreenManager.dispose(); - } + /** + * Dispose SdlManager and clean its resources + * <strong>Note: new instance of SdlManager should be created on every connection. SdlManager cannot be reused after getting disposed.</strong> + */ + @SuppressLint("NewApi") + @Override + public void dispose() { + if (this.permissionManager != null) { + this.permissionManager.dispose(); + } - if (this.screenManager != null) { - this.screenManager.dispose(); - } + if (this.fileManager != null) { + this.fileManager.dispose(); + } - if(this.videoStreamManager != null) { - this.videoStreamManager.dispose(); - } + if (this.lockScreenManager != null) { + this.lockScreenManager.dispose(); + } - // SuppressLint("NewApi") is used because audioStreamManager is only available on android >= jelly bean - if (this.audioStreamManager != null) { - this.audioStreamManager.dispose(); - } + if (this.screenManager != null) { + this.screenManager.dispose(); + } - if (this.proxy != null && !proxy.isDisposed()) { - try { - this.proxy.dispose(); - } catch (SdlException e) { - DebugTool.logError("Issue disposing proxy in SdlManager", e); - } - } + if (this.videoStreamManager != null) { + this.videoStreamManager.dispose(); + } - if(managerListener != null){ - managerListener.onDestroy(); - managerListener = null; - } + // SuppressLint("NewApi") is used because audioStreamManager is only available on android >= jelly bean + if (this.audioStreamManager != null) { + this.audioStreamManager.dispose(); + } - transitionToState(BaseSubManager.SHUTDOWN); - } + if (this.lifecycleManager != null) { + this.lifecycleManager.stop(); + } - // MANAGER GETTERS + if (managerListener != null) { + managerListener.onDestroy(); + managerListener = null; + } - /** - * Gets the PermissionManager. <br> - * <strong>Note: PermissionManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a PermissionManager object - */ - public PermissionManager getPermissionManager() { - if (permissionManager.getState() != BaseSubManager.READY && permissionManager.getState() != BaseSubManager.LIMITED){ - Log.e(TAG,"PermissionManager should not be accessed because it is not in READY/LIMITED state"); - } - checkSdlManagerState(); - return permissionManager; - } + transitionToState(BaseSubManager.SHUTDOWN); + } - /** - * Gets the FileManager. <br> - * <strong>Note: FileManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a FileManager object - */ - public FileManager getFileManager() { - if (fileManager.getState() != BaseSubManager.READY && fileManager.getState() != BaseSubManager.LIMITED){ - Log.e(TAG, "FileManager should not be accessed because it is not in READY/LIMITED state"); - } - checkSdlManagerState(); - return fileManager; - } + // MANAGER GETTERS /** * Gets the VideoStreamManager. <br> - * The VideoStreamManager returned will only be not null if the registered app type is - * either NAVIGATION or PROJECTION. Once the VideoStreamManager is retrieved, its start() - * method will need to be called before use. + * The VideoStreamManager returned will only be not null if the registered app type is + * either NAVIGATION or PROJECTION. Once the VideoStreamManager is retrieved, its start() + * method will need to be called before use. * <br><br><strong>Note: VideoStreamManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> + * * @return a VideoStreamManager object attached to this SdlManager instance */ - public @Nullable + public @Nullable VideoStreamManager getVideoStreamManager() { - checkSdlManagerState(); - return videoStreamManager; - } + checkSdlManagerState(); + return videoStreamManager; + } /** * Gets the AudioStreamManager. <br> - * The AudioStreamManager returned will only be not null if the registered app type is - * either NAVIGATION or PROJECTION. Once the AudioStreamManager is retrieved, its start() - * method will need to be called before use. + * The AudioStreamManager returned will only be not null if the registered app type is + * either NAVIGATION or PROJECTION. Once the AudioStreamManager is retrieved, its start() + * method will need to be called before use. * <br><strong>Note: AudioStreamManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> + * * @return a AudioStreamManager object */ - public @Nullable AudioStreamManager getAudioStreamManager() { - checkSdlManagerState(); - return audioStreamManager; - } - - /** - * Gets the ScreenManager. <br> - * <strong>Note: ScreenManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a ScreenManager object - */ - public ScreenManager getScreenManager() { - if (screenManager.getState() != BaseSubManager.READY && screenManager.getState() != BaseSubManager.LIMITED){ - Log.e(TAG, "ScreenManager should not be accessed because it is not in READY/LIMITED state"); - } - checkSdlManagerState(); - return screenManager; - } - - /** - * Gets the LockScreenManager. <br> - * <strong>Note: LockScreenManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a LockScreenManager object - */ - public LockScreenManager getLockScreenManager() { - if (lockScreenManager.getState() != BaseSubManager.READY && lockScreenManager.getState() != BaseSubManager.LIMITED){ - Log.e(TAG, "LockScreenManager should not be accessed because it is not in READY/LIMITED state"); - } - checkSdlManagerState(); - return lockScreenManager; - } - - /** - * Gets the SystemCapabilityManager. <br> - * <strong>Note: SystemCapabilityManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a SystemCapabilityManager object - */ - public SystemCapabilityManager getSystemCapabilityManager(){ - return proxy.getSystemCapabilityManager(); - } - - /** - * Method to retrieve the RegisterAppInterface Response message that was sent back from the - * module. It contains various attributes about the connected module and can be used to adapt - * to different module types and their supported features. - * - * @return RegisterAppInterfaceResponse received from the module or null if the app has not yet - * registered with the module. - */ - @Override - public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse(){ - if(proxy != null){ - return proxy.getRegisterAppInterfaceResponse(); - } - return null; - } - - /** - * Get the current OnHMIStatus - * @return OnHMIStatus object represents the current OnHMIStatus - */ - @Override - public OnHMIStatus getCurrentHMIStatus(){ - if(this.proxy !=null ){ - return proxy.getCurrentHMIStatus(); - } - return null; - } - - /** - * Retrieves the auth token, if any, that was attached to the StartServiceACK for the RPC - * service from the module. For example, this should be used to login to a user account. - * @return the string representation of the auth token - */ - @Override - public String getAuthToken(){ - return this.proxy.getAuthToken(); - } - - // PROTECTED GETTERS - - protected LockScreenConfig getLockScreenConfig() { return lockScreenConfig; } - - protected FileManagerConfig getFileManagerConfig() { return fileManagerConfig; } - - // SENDING REQUESTS - - /** - * Send RPC Message - * @param message RPCMessage - */ - @Override - public void sendRPC(RPCMessage message) { - try{ - proxy.sendRPC(message); - }catch (SdlException exception){ - handleSdlException(exception); - } - } - - /** - * Takes a list of RPCMessages and sends it to SDL in a synchronous fashion. Responses are captured through callback on OnMultipleRequestListener. - * For sending requests asynchronously, use sendRequests <br> - * - * <strong>NOTE: This will override any listeners on individual RPCs</strong><br> - * - * <strong>ADDITIONAL NOTE: This only takes the type of RPCRequest for now, notifications and responses will be thrown out</strong> - * - * @param rpcs is the list of RPCMessages being sent - * @param listener listener for updates and completions - */ - @Override - public void sendSequentialRPCs(final List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener){ - - List<RPCRequest> rpcRequestList = new ArrayList<>(); - for (int i = 0; i < rpcs.size(); i++) { - if (rpcs.get(i) instanceof RPCRequest){ - rpcRequestList.add((RPCRequest)rpcs.get(i)); - } - } - - if (rpcRequestList.size() > 0) { - try{ - proxy.sendSequentialRequests(rpcRequestList, listener); - }catch (SdlException exception){ - handleSdlException(exception); - } - } - } - - /** - * Takes a list of RPCMessages and sends it to SDL. Responses are captured through callback on OnMultipleRequestListener. - * For sending requests synchronously, use sendSequentialRPCs <br> - * - * <strong>NOTE: This will override any listeners on individual RPCs</strong> <br> - * - * <strong>ADDITIONAL NOTE: This only takes the type of RPCRequest for now, notifications and responses will be thrown out</strong> - * - * @param rpcs is the list of RPCMessages being sent - * @param listener listener for updates and completions - */ - @Override - public void sendRPCs(List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener) { - - List<RPCRequest> rpcRequestList = new ArrayList<>(); - for (int i = 0; i < rpcs.size(); i++) { - if (rpcs.get(i) instanceof RPCRequest){ - rpcRequestList.add((RPCRequest)rpcs.get(i)); - } - } - - if (rpcRequestList.size() > 0) { - try{ - proxy.sendRequests(rpcRequestList, listener); - }catch (SdlException exception){ - handleSdlException(exception); - } - } - } - - private void handleSdlException(SdlException exception){ - if(exception != null){ - DebugTool.logError("Caught SdlException: " + exception.getSdlExceptionCause()); - // In the future this should handle logic to dispose the manager if it is an unrecoverable error - }else{ - DebugTool.logError("Caught SdlException" ); - } - } - - /** - * Add an OnRPCNotificationListener - * @param listener listener that will be called when a notification is received - */ - @Override - public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener){ - proxy.addOnRPCNotificationListener(notificationId,listener); - } - - /** - * Remove an OnRPCNotificationListener - * @param listener listener that was previously added - */ - @Override - public void removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener){ - proxy.removeOnRPCNotificationListener(notificationId, listener); - } - - /** - * Add an OnRPCRequestListener - * @param listener listener that will be called when a request is received - */ - @Override - public void addOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener){ - proxy.addOnRPCRequestListener(requestId,listener); - } - - /** - * Remove an OnRPCRequestListener - * @param listener listener that was previously added - */ - @Override - public void removeOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener){ - proxy.removeOnRPCRequestListener(requestId, listener); - } - - // LIFECYCLE / OTHER - - // STARTUP - - /** - * Starts up a SdlManager, and calls provided callback called once all BaseSubManagers are done setting up - */ - @SuppressWarnings("unchecked") - @Override - public void start(){ - if (proxy == null) { - try { - if(transport!= null && transport.getTransportType() == TransportType.MULTIPLEX){ - //Do the thing - MultiplexTransportConfig multiplexTransportConfig = (MultiplexTransportConfig)(transport); - final MultiplexTransportConfig.TransportListener devListener = multiplexTransportConfig.getTransportListener(); - multiplexTransportConfig.setTransportListener(new MultiplexTransportConfig.TransportListener() { - @Override - public void onTransportEvent(List<TransportRecord> connectedTransports, boolean audioStreamTransportAvail, boolean videoStreamTransportAvail) { - - //Pass to submanagers that need it - if(videoStreamManager != null){ - videoStreamManager.handleTransportUpdated(connectedTransports, audioStreamTransportAvail, videoStreamTransportAvail); - } - - if(audioStreamManager != null){ - audioStreamManager.handleTransportUpdated(connectedTransports, audioStreamTransportAvail, videoStreamTransportAvail); - } - //If the developer supplied a listener to start, it is time to call that - if(devListener != null){ - devListener.onTransportEvent(connectedTransports,audioStreamTransportAvail,videoStreamTransportAvail); - } - } - }); - - //If the requires audio support has not been set, it should be set to true if the - //app is a media app, and false otherwise - if(multiplexTransportConfig.requiresAudioSupport() == null){ - multiplexTransportConfig.setRequiresAudioSupport(isMediaApp); - } - } - - proxy = new SdlProxyBase(proxyBridge, context, appName, shortAppName, isMediaApp, hmiLanguage, - hmiLanguage, hmiTypes, appId, transport, vrSynonyms, ttsChunks, dayColorScheme, - nightColorScheme) {}; - proxy.setMinimumProtocolVersion(minimumProtocolVersion); - proxy.setMinimumRPCVersion(minimumRPCVersion); - if (sdlSecList != null && !sdlSecList.isEmpty()) { - proxy.setSdlSecurity(sdlSecList, serviceEncryptionListener); - } - //Setup the notification queue - initNotificationQueue(); - - } catch (SdlException e) { - transitionToState(BaseSubManager.ERROR); - if (managerListener != null) { - managerListener.onError("Unable to start manager", e); - } - } - } - } - - protected void setProxy(SdlProxyBase proxy){ - this.proxy = proxy; - } - - // INTERNAL INTERFACE - private ISdl _internalInterface = new ISdl() { - @Override - public void start() { - try{ - proxy.initializeProxy(); - }catch (SdlException e){ - e.printStackTrace(); - } - } - - @Override - public void stop() { - try{ - proxy.dispose(); - }catch (SdlException e){ - e.printStackTrace(); - } - } - - @Override - public boolean isConnected() { - return proxy.getIsConnected(); - } - - @Override - public void addServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { - proxy.addServiceListener(serviceType,sdlServiceListener); - } - - @Override - public void removeServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { - proxy.removeServiceListener(serviceType,sdlServiceListener); - } - - @Override - public void startVideoService(VideoStreamingParameters parameters, boolean encrypted) { - if(proxy.getIsConnected()){ - proxy.startVideoService(encrypted,parameters); - } - } + public @Nullable + AudioStreamManager getAudioStreamManager() { + checkSdlManagerState(); + return audioStreamManager; + } - @Override - public IVideoStreamListener startVideoStream(boolean isEncrypted, VideoStreamingParameters parameters){ - if(proxy.getIsConnected()){ - return proxy.startVideoStream(isEncrypted, parameters); - }else{ - DebugTool.logError("Unable to start video stream, proxy not connected"); - return null; - } - } - - @Override - public void stopVideoService() { - if(proxy.getIsConnected()){ - proxy.endVideoStream(); - } - } - - @Override - public void startAudioService(boolean isEncrypted, AudioStreamingCodec codec, - AudioStreamingParams params) { - if(proxy.getIsConnected()){ - proxy.startAudioStream(isEncrypted, codec, params); - } - } - - @Override - public void startAudioService(boolean encrypted) { - if(isConnected()){ - proxy.startService(SessionType.PCM, encrypted); - } - } - - @Override - public IAudioStreamListener startAudioStream(boolean isEncrypted, AudioStreamingCodec codec, - AudioStreamingParams params) { - return proxy.startAudioStream(isEncrypted, codec, params); - } - - @Override - public void stopAudioService() { - if(proxy.getIsConnected()){ - proxy.endAudioStream(); - } - } - - @Override - public void sendRPCRequest(RPCRequest message){ - try { - proxy.sendRPC(message); - } catch (SdlException e) { - e.printStackTrace(); - } - } - - @Override - public void sendRPC(RPCMessage message) { - try { - proxy.sendRPC(message); - } catch (SdlException e) { - e.printStackTrace(); - } - } - - @Override - public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { - try { - proxy.sendRequests(rpcs, listener); - } catch (SdlException e) { - e.printStackTrace(); - } - } - - @Override - public void sendRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { - try { - proxy.sendRequests(rpcs, listener); - } catch (SdlException e) { - e.printStackTrace(); - } - } - - @Override - public void sendSequentialRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { - try { - proxy.sendSequentialRequests(rpcs,listener); - } catch (SdlException e) { - DebugTool.logError("Issue sending sequential RPCs ", e); - } - } - - @Override - public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { - proxy.addOnRPCNotificationListener(notificationId,listener); - } - - @Override - public boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { - return proxy.removeOnRPCNotificationListener(notificationId,listener); - } - - @Override - public void addOnRPCRequestListener(FunctionID functionID, OnRPCRequestListener listener) { - proxy.addOnRPCRequestListener(functionID, listener); - } - - @Override - public boolean removeOnRPCRequestListener(FunctionID functionID, OnRPCRequestListener listener) { - return proxy.removeOnRPCRequestListener(functionID, listener); - } - - @Override - public void addOnRPCListener(final FunctionID responseId, final OnRPCListener listener) { - proxyBridge.addRpcListener(responseId, listener); - } - - @Override - public boolean removeOnRPCListener(final FunctionID responseId, final OnRPCListener listener) { - return proxyBridge.removeOnRPCListener(responseId, listener); - } - - @Override - public Object getCapability(SystemCapabilityType systemCapabilityType){ - return proxy.getCapability(systemCapabilityType); - } - - @Override - public void getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener) { - proxy.getCapability(systemCapabilityType, scListener); - } - - @Override - public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() { - return proxy.getRegisterAppInterfaceResponse(); - } - - @Override - public Object getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener, boolean forceUpdate) { - if (proxy != null && proxy.getSystemCapabilityManager() != null) { - return proxy.getSystemCapabilityManager().getCapability(systemCapabilityType, scListener, forceUpdate); - } - return null; - } - - @Override - public boolean isCapabilitySupported(SystemCapabilityType systemCapabilityType){ - return proxy.isCapabilitySupported(systemCapabilityType); - } - - @Override - public void addOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener) { - proxy.addOnSystemCapabilityListener(systemCapabilityType, listener); - } - - @Override - public boolean removeOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener) { - return proxy.removeOnSystemCapabilityListener(systemCapabilityType, listener); - } - - @Override - public boolean isTransportForServiceAvailable(SessionType serviceType) { - if(SessionType.NAV.equals(serviceType)){ - return proxy.isVideoStreamTransportAvailable(); - }else if(SessionType.PCM.equals(serviceType)){ - return proxy.isAudioStreamTransportAvailable(); - } - return false; - } - - @Override - public SdlMsgVersion getSdlMsgVersion(){ - try { - return proxy.getSdlMsgVersion(); - } catch (SdlException e) { - e.printStackTrace(); - } - return null; - } - - @Override - public @NonNull Version getProtocolVersion() { - if(proxy.getProtocolVersion() != null){ - return proxy.getProtocolVersion(); - }else{ - return new Version(1,0,0); - } - } - - @Override - public void startRPCEncryption() { - if (proxy != null) { - proxy.startProtectedRPCService(); - } - } - - }; - - - // BUILDER - public static class Builder { - SdlManager sdlManager; - - /** - * Builder for the SdlManager. Parameters in the constructor are required. - * @param context the current context - * @param appId the app's ID - * @param appName the app's name - * @param listener a SdlManagerListener object - */ - public Builder(@NonNull Context context, @NonNull final String appId, @NonNull final String appName, @NonNull final SdlManagerListener listener){ - sdlManager = new SdlManager(); - setContext(context); - setAppId(appId); - setAppName(appName); - setManagerListener(listener); - } - /** - * Builder for the SdlManager. Parameters in the constructor are required. - * @param context the current context - * @param appId the app's ID - * @param appName the app's name - * @param listener a SdlManagerListener object - */ - public Builder(@NonNull Context context, @NonNull final String appId, @NonNull final String appName, @NonNull BaseTransportConfig transport, @NonNull final SdlManagerListener listener){ - sdlManager = new SdlManager(); - setContext(context); - setAppId(appId); - setAppName(appName); - setTransportType(transport); - setManagerListener(listener); - } - - /** - * Sets the App ID - * @param appId - */ - public Builder setAppId(@NonNull final String appId){ - sdlManager.appId = appId; - return this; - } - - /** - * Sets the Application Name - * @param appName - */ - public Builder setAppName(@NonNull final String appName){ - sdlManager.appName = appName; - return this; - } - - /** - * Sets the Short Application Name - * @param shortAppName - */ - public Builder setShortAppName(final String shortAppName) { - sdlManager.shortAppName = shortAppName; - return this; - } - - /** - * Sets the minimum protocol version that will be permitted to connect. - * If the protocol version of the head unit connected is below this version, - * the app will disconnect with an EndService protocol message and will not register. - * @param minimumProtocolVersion the minimum Protocol spec version that should be accepted - */ - public Builder setMinimumProtocolVersion(final Version minimumProtocolVersion) { - sdlManager.minimumProtocolVersion = minimumProtocolVersion; - return this; - } - - /** - * The minimum RPC version that will be permitted to connect. - * If the RPC version of the head unit connected is below this version, an UnregisterAppInterface will be sent. - * @param minimumRPCVersion the minimum RPC spec version that should be accepted - */ - public Builder setMinimumRPCVersion(final Version minimumRPCVersion) { - sdlManager.minimumRPCVersion = minimumRPCVersion; - return this; - } - - /** - * Sets the Language of the App - * @param hmiLanguage the desired language to be used on the display/HMI of the connected module - */ - public Builder setLanguage(final Language hmiLanguage) { - sdlManager.hmiLanguage = hmiLanguage; - sdlManager.language = hmiLanguage; - return this; - } - - /** - * Sets the TemplateColorScheme for daytime - * @param dayColorScheme color scheme that will be used (if supported) when the display is - * in a "Day Mode" or similar. Should comprise of colors that contrast - * well during the day under sunlight. - */ - public Builder setDayColorScheme(final TemplateColorScheme dayColorScheme){ - sdlManager.dayColorScheme = dayColorScheme; - return this; - } - - /** - * Sets the TemplateColorScheme for nighttime - * @param nightColorScheme color scheme that will be used (if supported) when the display is - * in a "Night Mode" or similar. Should comprise of colors that - * contrast well during the night and are not brighter than average. - */ - public Builder setNightColorScheme(final TemplateColorScheme nightColorScheme){ - sdlManager.nightColorScheme = nightColorScheme; - return this; - } - - /** - * Sets the FileManagerConfig for the session.<br> - * <strong>Note: If not set, the default configuration value of 1 will be set for - * artworkRetryCount and fileRetryCount in FileManagerConfig</strong> - * @param fileManagerConfig - configuration options - */ - public Builder setFileManagerConfig (final FileManagerConfig fileManagerConfig){ - sdlManager.fileManagerConfig = fileManagerConfig; - return this; - } - - /** - * Sets the LockScreenConfig for the session. <br> - * <strong>Note: If not set, the default configuration will be used.</strong> - * @param lockScreenConfig - configuration options - */ - public Builder setLockScreenConfig (final LockScreenConfig lockScreenConfig){ - sdlManager.lockScreenConfig = lockScreenConfig; - return this; - } - - /** - * Sets the icon for the app on head unit / In-Vehicle-Infotainment system <br> - * @param sdlArtwork the icon that will be used to represent this application on the - * connected module - */ - public Builder setAppIcon(final SdlArtwork sdlArtwork){ - sdlManager.appIcon = sdlArtwork; - return this; - } - - /** - * Sets the vector of AppHMIType <br> - * <strong>Note: This should be an ordered list from most -> least relevant</strong> - * @param hmiTypes HMI types that represent this application. For example, if the app is a - * music player, the MEDIA HMIType should be included. - */ - public Builder setAppTypes(final Vector<AppHMIType> hmiTypes){ - - sdlManager.hmiTypes = hmiTypes; - - if (hmiTypes != null) { - sdlManager.isMediaApp = hmiTypes.contains(AppHMIType.MEDIA); - } - - return this; - } - - /** - * Sets the voice recognition synonyms that can be used to identify this application. - * @param vrSynonyms a vector of Strings that can be associated with this app. For example the app's name should - * be included as well as any phonetic spellings of the app name that might help the on-board - * VR system associated a users spoken word with the supplied synonyms. - */ - public Builder setVrSynonyms(final Vector<String> vrSynonyms) { - sdlManager.vrSynonyms = vrSynonyms; - return this; - } - - /** - * Sets the Text-To-Speech Name of the application. These TTSChunks might be used by the module as an audio - * representation of the app's name. - * @param ttsChunks the TTS chunks that can represent this app's name - */ - public Builder setTtsName(final Vector<TTSChunk> ttsChunks) { - sdlManager.ttsChunks = ttsChunks; - return this; - } - - /** - * This Object type may change with the transport refactor - * Sets the BaseTransportConfig - * @param transport the type of transport that should be used for this SdlManager instance. - */ - public Builder setTransportType(@NonNull BaseTransportConfig transport){ - sdlManager.transport = transport; - return this; - } - - /** - * Sets the Context - * @param context - */ - public Builder setContext(Context context){ - sdlManager.context = context; - return this; - } - - /** - * Sets the Security library - * @param secList The list of security class(es) - */ - @Deprecated - public Builder setSdlSecurity(List<Class<? extends SdlSecurityBase>> secList) { - sdlManager.sdlSecList = secList; - return this; - } - - /** - * Sets the security libraries and a callback to notify caller when there is update to encryption service - * @param secList The list of security class(es) - * @param listener The callback object - */ - public Builder setSdlSecurity(@NonNull List<Class<? extends SdlSecurityBase>> secList, ServiceEncryptionListener listener) { - sdlManager.sdlSecList = secList; - sdlManager.serviceEncryptionListener = listener; - return this; - } - - /** - * Set the SdlManager Listener - * @param listener the listener - */ - public Builder setManagerListener(@NonNull final SdlManagerListener listener){ - sdlManager.managerListener = listener; - return this; - } - - /** - * Set RPCNotification listeners. SdlManager will preload these listeners before any RPCs are sent/received. - * @param listeners a map of listeners that will be called when a notification is received. - * Key represents the FunctionID of the notification and value represents the listener - */ - public Builder setRPCNotificationListeners(Map<FunctionID, OnRPCNotificationListener> listeners){ - sdlManager.onRPCNotificationListeners = listeners; - return this; - } - - /** - * Build SdlManager ang get it ready to be started - * <strong>Note: new instance of SdlManager should be created on every connection. SdlManager cannot be reused after getting disposed.</strong> - * @return SdlManager instance that is ready to be started - */ - public SdlManager build() { - - if (sdlManager.appName == null) { - throw new IllegalArgumentException("You must specify an app name by calling setAppName"); - } - - if (sdlManager.appId == null) { - throw new IllegalArgumentException("You must specify an app ID by calling setAppId"); - } - - if (sdlManager.managerListener == null) { - throw new IllegalArgumentException("You must set a SdlManagerListener object"); - } - - if (sdlManager.transport == null) { - throw new IllegalArgumentException("You must set a transport type object"); - } - - if (sdlManager.hmiTypes == null) { - Vector<AppHMIType> hmiTypesDefault = new Vector<>(); - hmiTypesDefault.add(AppHMIType.DEFAULT); - sdlManager.hmiTypes = hmiTypesDefault; - sdlManager.isMediaApp = false; - } - - if (sdlManager.lockScreenConfig == null){ - // if lock screen params are not set, use default - sdlManager.lockScreenConfig = new LockScreenConfig(); - } - - if(sdlManager.fileManagerConfig == null){ - //if FileManagerConfig is not set use default - sdlManager.fileManagerConfig = new FileManagerConfig(); - } - - if (sdlManager.hmiLanguage == null){ - sdlManager.hmiLanguage = Language.EN_US; - sdlManager.language = Language.EN_US; - } - - if (sdlManager.minimumProtocolVersion == null){ - sdlManager.minimumProtocolVersion = new Version("1.0.0"); - } - - if (sdlManager.minimumRPCVersion == null){ - sdlManager.minimumRPCVersion = new Version("1.0.0"); - } - - sdlManager.transitionToState(BaseSubManager.SETTING_UP); - - return sdlManager; - } - } - - /** - * Start a secured RPC service - */ - public void startRPCEncryption() { - if (proxy != null) { - proxy.startProtectedRPCService(); - } - } + /** + * Gets the LockScreenManager. <br> + * <strong>Note: LockScreenManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> + * + * @return a LockScreenManager object + */ + public LockScreenManager getLockScreenManager() { + if (lockScreenManager.getState() != BaseSubManager.READY && lockScreenManager.getState() != BaseSubManager.LIMITED) { + Log.e(TAG, "LockScreenManager should not be accessed because it is not in READY/LIMITED state"); + } + checkSdlManagerState(); + return lockScreenManager; + } + + // PROTECTED GETTERS + protected LockScreenConfig getLockScreenConfig() { + return lockScreenConfig; + } + + // BUILDER + public static class Builder extends BaseSdlManager.Builder { + /** + * Builder for the SdlManager. Parameters in the constructor are required. + * + * @param context the current context + * @param appId the app's ID + * @param appName the app's name + * @param listener a SdlManagerListener object + */ + public Builder(@NonNull Context context, @NonNull final String appId, @NonNull final String appName, @NonNull final SdlManagerListener listener) { + super(appId, appName, listener); + setContext(context); + } + + /** + * Builder for the SdlManager. Parameters in the constructor are required. + * + * @param context the current context + * @param appId the app's ID + * @param appName the app's name + * @param listener a SdlManagerListener object + */ + public Builder(@NonNull Context context, @NonNull final String appId, @NonNull final String appName, @NonNull BaseTransportConfig transport, @NonNull final SdlManagerListener listener) { + super(appId, appName, listener); + setContext(context); + setTransportType(transport); + } + + /** + * Sets the LockScreenConfig for the session. <br> + * <strong>Note: If not set, the default configuration will be used.</strong> + * + * @param lockScreenConfig - configuration options + */ + public Builder setLockScreenConfig(final LockScreenConfig lockScreenConfig) { + sdlManager.lockScreenConfig = lockScreenConfig; + return this; + } + + /** + * Sets the Context + * + * @param context the current context + */ + public Builder setContext(Context context) { + sdlManager.context = context; + return this; + } + + /** + * Build SdlManager ang get it ready to be started + * <strong>Note: new instance of SdlManager should be created on every connection. SdlManager cannot be reused after getting disposed.</strong> + * + * @return SdlManager instance that is ready to be started + */ + public SdlManager build() { + if (sdlManager.transport == null) { + throw new IllegalArgumentException("You must set a transport type object"); + } + + if (sdlManager.lockScreenConfig == null) { + // if lock screen params are not set, use default + sdlManager.lockScreenConfig = new LockScreenConfig(); + } + + super.build(); + + return sdlManager; + } + } } diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/EncryptionLifecycleManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/EncryptionLifecycleManager.java new file mode 100644 index 000000000..99723b456 --- /dev/null +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/EncryptionLifecycleManager.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019 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.managers.lifecycle; + +import android.support.annotation.NonNull; + +import com.smartdevicelink.managers.ServiceEncryptionListener; +import com.smartdevicelink.protocol.enums.SessionType; +import com.smartdevicelink.proxy.interfaces.ISdl; + +class EncryptionLifecycleManager extends BaseEncryptionLifecycleManager { + + EncryptionLifecycleManager(@NonNull ISdl internalInterface, ServiceEncryptionListener listener) { + super(internalInterface, listener); + internalInterface.addServiceListener(SessionType.NAV, securedServiceListener); + internalInterface.addServiceListener(SessionType.PCM, securedServiceListener); + } +} diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java new file mode 100644 index 000000000..777e29f1b --- /dev/null +++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2019 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.managers.lifecycle; + +import android.app.Service; +import android.content.Context; +import android.support.annotation.RestrictTo; +import android.util.Log; + +import com.smartdevicelink.SdlConnection.SdlSession; +import com.smartdevicelink.SdlConnection.SdlSession2; +import com.smartdevicelink.exception.SdlException; +import com.smartdevicelink.exception.SdlExceptionCause; +import com.smartdevicelink.protocol.enums.SessionType; +import com.smartdevicelink.proxy.interfaces.ISdlServiceListener; +import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason; +import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.security.SdlSecurityBase; +import com.smartdevicelink.streaming.video.VideoStreamingParameters; +import com.smartdevicelink.transport.BaseTransportConfig; +import com.smartdevicelink.transport.MultiplexTransportConfig; +import com.smartdevicelink.transport.TCPTransportConfig; +import com.smartdevicelink.transport.USBTransportConfig; +import com.smartdevicelink.transport.enums.TransportType; +import com.smartdevicelink.util.DebugTool; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.FutureTask; +import java.util.concurrent.ScheduledExecutorService; + +/** + * The lifecycle manager creates a central point for all SDL session logic to converge. It should only be used by + * the library itself. Usage outside the library is not permitted and will not be protected for in the future. + * + * @author Bilal Alsharifi. + */ +@RestrictTo(RestrictTo.Scope.LIBRARY) +public class LifecycleManager extends BaseLifecycleManager { + private static final int RESPONSE_WAIT_TIME = 2000; + private ISdlServiceListener navServiceListener; + private boolean navServiceStartResponseReceived = false; + private boolean navServiceStartResponse = false; + private boolean navServiceEndResponseReceived = false; + private boolean navServiceEndResponse = false; + private boolean pcmServiceEndResponseReceived = false; + private boolean pcmServiceEndResponse = false; + private Context context; + + public LifecycleManager(AppConfig appConfig, BaseTransportConfig config, LifecycleListener listener) { + super(appConfig, config, listener); + } + + @Override + void initializeProxy() { + super.initializeProxy(); + + //Handle legacy USB connections + if (_transportConfig != null && TransportType.USB.equals(_transportConfig.getTransportType())) { + //A USB transport config was provided + USBTransportConfig usbTransportConfig = (USBTransportConfig) _transportConfig; + if (usbTransportConfig.getUsbAccessory() == null) { + DebugTool.logInfo("Legacy USB transport config was used, but received null for accessory. Attempting to connect with router service"); + //The accessory was null which means it came from a router service + MultiplexTransportConfig multiplexTransportConfig = new MultiplexTransportConfig(usbTransportConfig.getUSBContext(), appConfig.getAppID()); + multiplexTransportConfig.setRequiresHighBandwidth(true); + multiplexTransportConfig.setSecurityLevel(MultiplexTransportConfig.FLAG_MULTI_SECURITY_OFF); + multiplexTransportConfig.setPrimaryTransports(Collections.singletonList(TransportType.USB)); + multiplexTransportConfig.setSecondaryTransports(new ArrayList<TransportType>()); + _transportConfig = multiplexTransportConfig; + } + } + + if (_transportConfig != null && _transportConfig.getTransportType().equals(TransportType.MULTIPLEX)) { + this.session = new SdlSession2(sdlConnectionListener, (MultiplexTransportConfig) _transportConfig); + } else if (_transportConfig != null && _transportConfig.getTransportType().equals(TransportType.TCP)) { + this.session = new SdlSession2(sdlConnectionListener, (TCPTransportConfig) _transportConfig); + } else { + this.session = SdlSession.createSession((byte) getProtocolVersion().getMajor(), sdlConnectionListener, _transportConfig); + } + } + + private void cycleProxy(SdlDisconnectedReason disconnectedReason) { + cleanProxy(); + initializeProxy(); + if (!SdlDisconnectedReason.LEGACY_BLUETOOTH_MODE_ENABLED.equals(disconnectedReason) && !SdlDisconnectedReason.PRIMARY_TRANSPORT_CYCLE_REQUEST.equals(disconnectedReason)) { + //We don't want to alert higher if we are just cycling for legacy bluetooth + onClose("Sdl Proxy Cycled", new SdlException("Sdl Proxy Cycled", SdlExceptionCause.SDL_PROXY_CYCLED)); + } + try { + session.startSession(); + } catch (SdlException e) { + e.printStackTrace(); + } + } + + @RestrictTo(RestrictTo.Scope.LIBRARY) + public void setContext(Context context) { + this.context = context; + } + + @Override + void setSdlSecurityStaticVars() { + super.setSdlSecurityStaticVars(); + + Service service = null; + if (context != null && context instanceof Service) { + service = (Service) context; + } + SdlSecurityBase.setAppService(service); + SdlSecurityBase.setContext(context); + } + + @Override + void onProtocolSessionStarted(SessionType sessionType) { + super.onProtocolSessionStarted(sessionType); + if (sessionType.eq(SessionType.NAV)) { + navServiceStartResponseReceived = true; + navServiceStartResponse = true; + } + } + + @Override + void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig) { + super.onTransportDisconnected(info, availablePrimary, transportConfig); + if (availablePrimary) { + _transportConfig = transportConfig; + Log.d(TAG, "notifying RPC session ended, but potential primary transport available"); + cycleProxy(SdlDisconnectedReason.PRIMARY_TRANSPORT_CYCLE_REQUEST); + } else { + onClose(info, null); + } + } + + @Override + void onProtocolSessionStartedNACKed(SessionType sessionType) { + super.onProtocolSessionStartedNACKed(sessionType); + if (sessionType.eq(SessionType.NAV)) { + navServiceStartResponseReceived = true; + navServiceStartResponse = false; + } + } + + @Override + void onProtocolSessionEnded(SessionType sessionType) { + super.onProtocolSessionEnded(sessionType); + if (sessionType.eq(SessionType.NAV)) { + navServiceEndResponseReceived = true; + navServiceEndResponse = true; + } else if (sessionType.eq(SessionType.PCM)) { + pcmServiceEndResponseReceived = true; + pcmServiceEndResponse = true; + } + } + + @Override + void onProtocolSessionEndedNACKed(SessionType sessionType) { + super.onProtocolSessionEndedNACKed(sessionType); + if (sessionType.eq(SessionType.NAV)) { + navServiceEndResponseReceived = true; + navServiceEndResponse = false; + } else if (sessionType.eq(SessionType.PCM)) { + pcmServiceEndResponseReceived = true; + pcmServiceEndResponse = false; + } + } + + /** + * This method will try to start the video service with the requested parameters. + * When it returns it will attempt to store the accepted parameters if available. + * + * @param isEncrypted if the service should be encrypted + * @param parameters the desired video streaming parameters + */ + @Override + void startVideoService(boolean isEncrypted, VideoStreamingParameters parameters) { + if (session == null) { + DebugTool.logWarning("SdlSession is not created yet."); + return; + } + if (!session.getIsConnected()) { + DebugTool.logWarning("Connection is not available."); + return; + } + + session.setDesiredVideoParams(parameters); + tryStartVideoStream(isEncrypted, parameters); + } + + /** + * Try to open a video service by using the video streaming parameters supplied. + * Only information from codecs, width and height are used during video format negotiation. + * + * @param isEncrypted Specify true if packets on this service have to be encrypted + * @param parameters VideoStreamingParameters that are desired. Does not guarantee this is what will be accepted. + * @return If the service is opened successfully, an instance of VideoStreamingParams is + * returned which contains accepted video format. If the service is opened with legacy + * mode (i.e. without any negotiation) then an instance of VideoStreamingParams is + * returned. If the service was not opened then null is returned. + */ + private VideoStreamingParameters tryStartVideoStream(boolean isEncrypted, VideoStreamingParameters parameters) { + if (session == null) { + DebugTool.logWarning("SdlSession is not created yet."); + return null; + } + if (getProtocolVersion() != null && getProtocolVersion().getMajor() >= 5 && !systemCapabilityManager.isCapabilitySupported(SystemCapabilityType.VIDEO_STREAMING)) { + DebugTool.logWarning("Module doesn't support video streaming."); + return null; + } + if (parameters == null) { + DebugTool.logWarning("Video parameters were not supplied."); + return null; + } + + if (!navServiceStartResponseReceived || !navServiceStartResponse //If we haven't started the service before + || (navServiceStartResponse && isEncrypted && !session.isServiceProtected(SessionType.NAV))) { //Or the service has been started but we'd like to start an encrypted one + session.setDesiredVideoParams(parameters); + + navServiceStartResponseReceived = false; + navServiceStartResponse = false; + + session.startService(SessionType.NAV, session.getSessionId(), isEncrypted); + addNavListener(); + FutureTask<Void> fTask = new FutureTask<>(new CallableMethod(RESPONSE_WAIT_TIME)); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.execute(fTask); + + //noinspection StatementWithEmptyBody + while (!navServiceStartResponseReceived && !fTask.isDone()) ; + scheduler.shutdown(); + } + + if (navServiceStartResponse) { + if (getProtocolVersion() != null && getProtocolVersion().getMajor() < 5) { //Versions 1-4 do not support streaming parameter negotiations + session.setAcceptedVideoParams(parameters); + } + return session.getAcceptedVideoParams(); + } + + return null; + } + + private void addNavListener() { + // videos may be started and stopped. Only add this once + if (navServiceListener == null) { + + navServiceListener = new ISdlServiceListener() { + @Override + public void onServiceStarted(SdlSession session, SessionType type, boolean isEncrypted) { + } + + @Override + public void onServiceEnded(SdlSession session, SessionType type) { + // reset nav flags so nav can start upon the next transport connection + navServiceStartResponseReceived = false; + navServiceStartResponse = false; + } + + @Override + public void onServiceError(SdlSession session, SessionType type, String reason) { + // if there is an error reset the flags so that there is a chance to restart streaming + navServiceStartResponseReceived = false; + navServiceStartResponse = false; + } + }; + session.addServiceListener(SessionType.NAV, navServiceListener); + } + } + + /** + * Closes the opened video service (serviceType 11) + * + * @return true if the video service is closed successfully, return false otherwise + */ + @Override + boolean endVideoStream() { + if (session == null) { + DebugTool.logWarning("SdlSession is not created yet."); + return false; + } + if (!session.getIsConnected()) { + DebugTool.logWarning("Connection is not available."); + return false; + } + + navServiceEndResponseReceived = false; + navServiceEndResponse = false; + session.stopVideoStream(); + + FutureTask<Void> fTask = new FutureTask<>(new CallableMethod(RESPONSE_WAIT_TIME)); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.execute(fTask); + + //noinspection StatementWithEmptyBody + while (!navServiceEndResponseReceived && !fTask.isDone()) ; + scheduler.shutdown(); + + return navServiceEndResponse; + } + + @Override + void startAudioService(boolean isEncrypted) { + if (session == null) { + DebugTool.logWarning("SdlSession is not created yet."); + return; + } + if (!session.getIsConnected()) { + DebugTool.logWarning("Connection is not available."); + return; + } + session.startService(SessionType.PCM, session.getSessionId(), isEncrypted); + } + + /** + * Closes the opened audio service (serviceType 10) + * + * @return true if the audio service is closed successfully, return false otherwise + */ + @Override + boolean endAudioStream() { + if (session == null) { + DebugTool.logWarning("SdlSession is not created yet."); + return false; + } + if (!session.getIsConnected()) { + DebugTool.logWarning("Connection is not available."); + return false; + } + + pcmServiceEndResponseReceived = false; + pcmServiceEndResponse = false; + session.stopAudioStream(); + + FutureTask<Void> fTask = new FutureTask<>(new CallableMethod(RESPONSE_WAIT_TIME)); + ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + scheduler.execute(fTask); + + //noinspection StatementWithEmptyBody + while (!pcmServiceEndResponseReceived && !fTask.isDone()) ; + scheduler.shutdown(); + + return pcmServiceEndResponse; + } + + private class CallableMethod implements Callable<Void> { + private final long waitTime; + + public CallableMethod(int timeInMillis) { + this.waitTime = timeInMillis; + } + + @Override + public Void call() { + try { + Thread.sleep(waitTime); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return null; + } + } +} diff --git a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java index df1c18d42..c52f27b2d 100644 --- a/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/BaseSdlManager.java @@ -31,24 +31,46 @@ */ package com.smartdevicelink.managers; +import android.support.annotation.NonNull; import android.util.Log; +import com.smartdevicelink.managers.file.FileManager; +import com.smartdevicelink.managers.file.FileManagerConfig; +import com.smartdevicelink.managers.file.filetypes.SdlArtwork; +import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate; +import com.smartdevicelink.managers.lifecycle.LifecycleManager; +import com.smartdevicelink.managers.permission.PermissionManager; +import com.smartdevicelink.managers.screen.ScreenManager; import com.smartdevicelink.protocol.enums.FunctionID; +import com.smartdevicelink.protocol.enums.SessionType; import com.smartdevicelink.proxy.RPCMessage; import com.smartdevicelink.proxy.RPCNotification; +import com.smartdevicelink.proxy.RPCRequest; +import com.smartdevicelink.proxy.RPCResponse; +import com.smartdevicelink.proxy.SystemCapabilityManager; +import com.smartdevicelink.proxy.interfaces.ISdl; +import com.smartdevicelink.proxy.rpc.ChangeRegistration; import com.smartdevicelink.proxy.rpc.OnHMIStatus; import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; +import com.smartdevicelink.proxy.rpc.SetAppIcon; import com.smartdevicelink.proxy.rpc.TTSChunk; import com.smartdevicelink.proxy.rpc.TemplateColorScheme; import com.smartdevicelink.proxy.rpc.enums.AppHMIType; import com.smartdevicelink.proxy.rpc.enums.Language; +import com.smartdevicelink.proxy.rpc.enums.Result; +import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason; import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; import com.smartdevicelink.proxy.rpc.listeners.OnRPCRequestListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener; +import com.smartdevicelink.security.SdlSecurityBase; import com.smartdevicelink.transport.BaseTransportConfig; import com.smartdevicelink.util.DebugTool; import com.smartdevicelink.util.Version; +import org.json.JSONException; + +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Queue; @@ -58,13 +80,9 @@ import java.util.concurrent.ConcurrentLinkedQueue; abstract class BaseSdlManager { - private static final String TAG = "BaseSubManager"; - + static final String TAG = "BaseSubManager"; final Object STATE_LOCK = new Object(); int state = -1; - - static final int MAX_RETRY = 3; - int changeRegistrationRetry = 0; String appId, appName, shortAppName; boolean isMediaApp; Language hmiLanguage; @@ -76,41 +94,153 @@ abstract class BaseSdlManager { TemplateColorScheme dayColorScheme, nightColorScheme; Version minimumProtocolVersion; Version minimumRPCVersion; - Queue<RPCNotification> queuedNotifications = null; OnRPCNotificationListener queuedNotificationListener = null; Map<FunctionID, OnRPCNotificationListener> onRPCNotificationListeners; + SdlArtwork appIcon; + SdlManagerListener managerListener; + List<Class<? extends SdlSecurityBase>> sdlSecList; + ServiceEncryptionListener serviceEncryptionListener; + FileManagerConfig fileManagerConfig; + int changeRegistrationRetry = 0; + static final int MAX_RETRY = 3; - // PROTECTED GETTERS - protected String getAppName() { return appName; } + // Managers + LifecycleManager lifecycleManager; + PermissionManager permissionManager; + FileManager fileManager; + ScreenManager screenManager; + + // INTERNAL INTERFACE + /** + * This is from the LifeCycleManager directly. In the future if there is a reason to be a man in the middle + * the SdlManager could create it's own, however right now it was only a duplication of logic tied to the LCM. + */ + ISdl _internalInterface; + + // Initialize with anonymous lifecycleListener + final LifecycleManager.LifecycleListener lifecycleListener = new LifecycleManager.LifecycleListener() { + @Override + public void onProxyConnected(LifecycleManager lifeCycleManager) { + Log.i(TAG, "Proxy is connected. Now initializing."); + synchronized (this) { + changeRegistrationRetry = 0; + checkLifecycleConfiguration(); + initialize(); + } + } - protected String getAppId() { return appId; } + @Override + public void onServiceStarted(SessionType sessionType) { - protected String getShortAppName() { return shortAppName; } + } - protected Version getMinimumProtocolVersion() { return minimumProtocolVersion; } + @Override + public void onServiceEnded(SessionType sessionType) { - protected Version getMinimumRPCVersion() { return minimumRPCVersion; } + } - protected Language getHmiLanguage() { return hmiLanguage; } + @Override + public void onProxyClosed(LifecycleManager lifeCycleManager, String info, Exception e, SdlDisconnectedReason reason) { + BaseSdlManager.this.onProxyClosed(reason); + } - protected Language getLanguage() { return language; } + @Override + public void onError(LifecycleManager lifeCycleManager, String info, Exception e) { - protected TemplateColorScheme getDayColorScheme() { return dayColorScheme; } + } + }; + + // Sub manager listener + final CompletionListener subManagerListener = new CompletionListener() { + @Override + public synchronized void onComplete(boolean success) { + if (!success) { + Log.e(TAG, "Sub manager failed to initialize"); + } + checkState(); + } + }; - protected TemplateColorScheme getNightColorScheme() { return nightColorScheme; } + // ABSTRACT METHODS + abstract void retryChangeRegistration(); - protected Vector<AppHMIType> getAppTypes() { return hmiTypes; } + abstract void onProxyClosed(SdlDisconnectedReason reason); - protected Vector<String> getVrSynonyms() { return vrSynonyms; } + abstract void checkState(); - protected Vector<TTSChunk> getTtsChunks() { return ttsChunks; } + abstract void initialize(); - protected BaseTransportConfig getTransport() { return transport; } + public abstract void dispose(); + protected void checkLifecycleConfiguration() { + final Language actualLanguage = this.getRegisterAppInterfaceResponse().getLanguage(); + final Language actualHMILanguage = this.getRegisterAppInterfaceResponse().getHmiDisplayLanguage(); + + if ((actualLanguage != null && !actualLanguage.equals(language)) || (actualHMILanguage != null && !actualHMILanguage.equals(hmiLanguage))) { + + LifecycleConfigurationUpdate lcuNew = managerListener.managerShouldUpdateLifecycle(actualLanguage, actualHMILanguage); + LifecycleConfigurationUpdate lcuOld = managerListener.managerShouldUpdateLifecycle(actualLanguage); + final LifecycleConfigurationUpdate lcu; + ChangeRegistration changeRegistration; + if (lcuNew == null) { + lcu = lcuOld; + changeRegistration = new ChangeRegistration(actualLanguage, actualLanguage); + } else { + lcu = lcuNew; + changeRegistration = new ChangeRegistration(actualLanguage, actualHMILanguage); + } + + if (lcu != null) { + changeRegistration.setAppName(lcu.getAppName()); + changeRegistration.setNgnMediaScreenAppName(lcu.getShortAppName()); + changeRegistration.setTtsName(lcu.getTtsName()); + changeRegistration.setVrSynonyms(lcu.getVoiceRecognitionCommandNames()); + changeRegistration.setOnRPCResponseListener(new OnRPCResponseListener() { + @Override + public void onResponse(int correlationId, RPCResponse response) { + if (response.getSuccess()) { + // go through and change sdlManager properties that were changed via the LCU update + hmiLanguage = actualHMILanguage; + language = actualLanguage; + + if (lcu.getAppName() != null) { + appName = lcu.getAppName(); + } + + if (lcu.getShortAppName() != null) { + shortAppName = lcu.getShortAppName(); + } + + if (lcu.getTtsName() != null) { + ttsChunks = lcu.getTtsName(); + } + + if (lcu.getVoiceRecognitionCommandNames() != null) { + vrSynonyms = lcu.getVoiceRecognitionCommandNames(); + } + } + try { + DebugTool.logInfo(response.serializeJSON().toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + DebugTool.logError("Change Registration onError: " + resultCode + " | Info: " + info); + retryChangeRegistration(); + } + }); + this.sendRPC(changeRegistration); + } + } + } /** * Get the current state for the SdlManager + * * @return int value that represents the current state * @see BaseSubManager */ @@ -126,13 +256,13 @@ abstract class BaseSdlManager { } } - void checkSdlManagerState(){ - if (getState() != BaseSubManager.READY && getState() != BaseSubManager.LIMITED){ + void checkSdlManagerState() { + if (getState() != BaseSubManager.READY && getState() != BaseSubManager.LIMITED) { Log.e(TAG, "SdlManager is not ready for use, be sure to initialize with start() method, implement callback, and use SubManagers in the SdlManager's callback"); } } - void initNotificationQueue(){ + void initNotificationQueue() { //Setup the notification queue if (onRPCNotificationListeners != null) { Set<FunctionID> functionIDSet = onRPCNotificationListeners.keySet(); @@ -151,7 +281,7 @@ abstract class BaseSdlManager { } } - void handleQueuedNotifications(){ + void handleQueuedNotifications() { //Handle queued notifications and add the listeners if (onRPCNotificationListeners != null) { Set<FunctionID> functionIDSet = onRPCNotificationListeners.keySet(); @@ -184,39 +314,564 @@ abstract class BaseSdlManager { } } - abstract void checkState(); + /** + * Starts up a SdlManager, and calls provided callback called once all BaseSubManagers are done setting up + */ + @SuppressWarnings("unchecked") + public void start() { + LifecycleManager.AppConfig appConfig = new LifecycleManager.AppConfig(); + appConfig.setAppName(appName); + //short app name + appConfig.setMediaApp(isMediaApp); + appConfig.setHmiDisplayLanguageDesired(hmiLanguage); + appConfig.setLanguageDesired(hmiLanguage); + appConfig.setAppType(hmiTypes); + appConfig.setVrSynonyms(vrSynonyms); + appConfig.setTtsName(ttsChunks); + appConfig.setDayColorScheme(dayColorScheme); + appConfig.setNightColorScheme(nightColorScheme); + appConfig.setAppID(appId); + appConfig.setMinimumProtocolVersion(minimumProtocolVersion); + appConfig.setMinimumRPCVersion(minimumRPCVersion); + + lifecycleManager = new LifecycleManager(appConfig, transport, lifecycleListener); + _internalInterface = lifecycleManager.getInternalInterface((SdlManager) BaseSdlManager.this); + + if (sdlSecList != null && !sdlSecList.isEmpty()) { + lifecycleManager.setSdlSecurity(sdlSecList, serviceEncryptionListener); + } - protected abstract void initialize(); - protected abstract void checkLifecycleConfiguration(); + //Setup the notification queue + initNotificationQueue(); + } - //Public abstract API - public abstract void start(); - public abstract void dispose(); - public abstract void sendRPC(RPCMessage message); - public abstract void sendSequentialRPCs(final List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener); - public abstract void sendRPCs(List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener); - public abstract RegisterAppInterfaceResponse getRegisterAppInterfaceResponse(); - public abstract OnHMIStatus getCurrentHMIStatus(); - public abstract String getAuthToken(); + void onReady() { + // Set the app icon + if (BaseSdlManager.this.appIcon != null && BaseSdlManager.this.appIcon.getName() != null) { + if (fileManager != null && fileManager.getState() == BaseSubManager.READY && !fileManager.hasUploadedFile(BaseSdlManager.this.appIcon)) { + fileManager.uploadArtwork(BaseSdlManager.this.appIcon, new CompletionListener() { + @Override + public void onComplete(boolean success) { + if (success) { + SetAppIcon msg = new SetAppIcon(BaseSdlManager.this.appIcon.getName()); + _internalInterface.sendRPC(msg); + } + } + }); + } else { + SetAppIcon msg = new SetAppIcon(BaseSdlManager.this.appIcon.getName()); + _internalInterface.sendRPC(msg); + } + } + } + + // PROTECTED GETTERS + protected String getAppName() { + return appName; + } - public abstract void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener); + protected String getAppId() { + return appId; + } + + protected String getShortAppName() { + return shortAppName; + } + + protected Version getMinimumProtocolVersion() { + return minimumProtocolVersion; + } + + protected Version getMinimumRPCVersion() { + return minimumRPCVersion; + } + + protected Language getHmiLanguage() { + return hmiLanguage; + } + + protected Language getLanguage() { + return language; + } + + protected TemplateColorScheme getDayColorScheme() { + return dayColorScheme; + } + + protected TemplateColorScheme getNightColorScheme() { + return nightColorScheme; + } + + protected Vector<AppHMIType> getAppTypes() { + return hmiTypes; + } + + protected Vector<String> getVrSynonyms() { + return vrSynonyms; + } + + protected Vector<TTSChunk> getTtsChunks() { + return ttsChunks; + } + + protected BaseTransportConfig getTransport() { + return transport; + } + + protected FileManagerConfig getFileManagerConfig() { + return fileManagerConfig; + } + + // MANAGER GETTERS + + /** + * Gets the PermissionManager. <br> + * <strong>Note: PermissionManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> + * + * @return a PermissionManager object + */ + public PermissionManager getPermissionManager() { + if (permissionManager.getState() != BaseSubManager.READY && permissionManager.getState() != BaseSubManager.LIMITED) { + Log.e(TAG, "PermissionManager should not be accessed because it is not in READY/LIMITED state"); + } + checkSdlManagerState(); + return permissionManager; + } + + /** + * Gets the FileManager. <br> + * <strong>Note: FileManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> + * + * @return a FileManager object + */ + public FileManager getFileManager() { + if (fileManager.getState() != BaseSubManager.READY && fileManager.getState() != BaseSubManager.LIMITED) { + Log.e(TAG, "FileManager should not be accessed because it is not in READY/LIMITED state"); + } + checkSdlManagerState(); + return fileManager; + } + + /** + * Gets the ScreenManager. <br> + * <strong>Note: ScreenManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> + * + * @return a ScreenManager object + */ + public ScreenManager getScreenManager() { + if (screenManager.getState() != BaseSubManager.READY && screenManager.getState() != BaseSubManager.LIMITED) { + Log.e(TAG, "ScreenManager should not be accessed because it is not in READY/LIMITED state"); + } + checkSdlManagerState(); + return screenManager; + } + + /** + * Gets the SystemCapabilityManager. <br> + * <strong>Note: SystemCapabilityManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> + * + * @return a SystemCapabilityManager object + */ + public SystemCapabilityManager getSystemCapabilityManager() { + return lifecycleManager.getSystemCapabilityManager((SdlManager) this); + } + + /** + * Method to retrieve the RegisterAppInterface Response message that was sent back from the + * module. It contains various attributes about the connected module and can be used to adapt + * to different module types and their supported features. + * + * @return RegisterAppInterfaceResponse received from the module or null if the app has not yet + * registered with the module. + */ + public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() { + if (lifecycleManager != null) { + return lifecycleManager.getRegisterAppInterfaceResponse(); + } + return null; + } + + /** + * Get the current OnHMIStatus + * + * @return OnHMIStatus object represents the current OnHMIStatus + */ + public OnHMIStatus getCurrentHMIStatus() { + if (this.lifecycleManager != null) { + return lifecycleManager.getCurrentHMIStatus(); + } + return null; + } + + /** + * Retrieves the auth token, if any, that was attached to the StartServiceACK for the RPC + * service from the module. For example, this should be used to login to a user account. + * + * @return the string representation of the auth token + */ + public String getAuthToken() { + return this.lifecycleManager.getAuthToken(); + } + + // SENDING REQUESTS + + /** + * Send RPC Message <br> + * + * @param message RPCMessage + */ + public void sendRPC(RPCMessage message) { + _internalInterface.sendRPC(message); + } + + /** + * Takes a list of RPCMessages and sends it to SDL in a synchronous fashion. Responses are captured through callback on OnMultipleRequestListener. + * For sending requests asynchronously, use sendRPCs <br> + * + * <strong>NOTE: This will override any listeners on individual RPCs</strong><br> + * + * <strong>ADDITIONAL NOTE: This only takes the type of RPCRequest for now, notifications and responses will be thrown out</strong> + * + * @param rpcs is the list of RPCMessages being sent + * @param listener listener for updates and completions + */ + public void sendSequentialRPCs(final List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener) { + List<RPCRequest> rpcRequestList = new ArrayList<>(); + for (int i = 0; i < rpcs.size(); i++) { + if (rpcs.get(i) instanceof RPCRequest) { + rpcRequestList.add((RPCRequest) rpcs.get(i)); + } + } + + if (rpcRequestList.size() > 0) { + _internalInterface.sendSequentialRPCs(rpcRequestList, listener); + } + } + + /** + * Takes a list of RPCMessages and sends it to SDL. Responses are captured through callback on OnMultipleRequestListener. + * For sending requests synchronously, use sendSequentialRPCs <br> + * + * <strong>NOTE: This will override any listeners on individual RPCs</strong> <br> + * + * <strong>ADDITIONAL NOTE: This only takes the type of RPCRequest for now, notifications and responses will be thrown out</strong> + * + * @param rpcs is the list of RPCMessages being sent + * @param listener listener for updates and completions + */ + public void sendRPCs(List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener) { + List<RPCRequest> rpcRequestList = new ArrayList<>(); + for (int i = 0; i < rpcs.size(); i++) { + if (rpcs.get(i) instanceof RPCRequest) { + rpcRequestList.add((RPCRequest) rpcs.get(i)); + } + } + + if (rpcRequestList.size() > 0) { + _internalInterface.sendRPCs(rpcRequestList, listener); + } + } + + /** + * Add an OnRPCNotificationListener + * + * @param listener listener that will be called when a notification is received + */ + public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + _internalInterface.addOnRPCNotificationListener(notificationId, listener); + } /** * Remove an OnRPCNotificationListener + * * @param listener listener that was previously added */ - public abstract void removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener); + public void removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + _internalInterface.removeOnRPCNotificationListener(notificationId, listener); + } /** * Add an OnRPCRequestListener + * * @param listener listener that will be called when a request is received */ - public abstract void addOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener); + public void addOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener) { + _internalInterface.addOnRPCRequestListener(requestId, listener); + } /** * Remove an OnRPCRequestListener + * * @param listener listener that was previously added */ - public abstract void removeOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener); + public void removeOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener) { + _internalInterface.removeOnRPCRequestListener(requestId, listener); + } + + // BUILDER + public static class Builder { + SdlManager sdlManager; + + Builder(@NonNull final String appId, @NonNull final String appName, @NonNull final SdlManagerListener listener) { + sdlManager = new SdlManager(); + setAppId(appId); + setAppName(appName); + setManagerListener(listener); + } + + /** + * Sets the App ID + * + * @param appId String representation of the App ID retreived from the SDL Developer Portal + */ + public Builder setAppId(@NonNull final String appId) { + sdlManager.appId = appId; + return this; + } + + /** + * Sets the Application Name + * + * @param appName String that will be associated as the app's name + */ + public Builder setAppName(@NonNull final String appName) { + sdlManager.appName = appName; + return this; + } + + /** + * Sets the Short Application Name + * + * @param shortAppName a shorter representation of the app's name for smaller displays + */ + public Builder setShortAppName(final String shortAppName) { + sdlManager.shortAppName = shortAppName; + return this; + } + + /** + * Sets the minimum protocol version that will be permitted to connect. + * If the protocol version of the head unit connected is below this version, + * the app will disconnect with an EndService protocol message and will not register. + * + * @param minimumProtocolVersion the minimum Protocol spec version that should be accepted + */ + public Builder setMinimumProtocolVersion(final Version minimumProtocolVersion) { + sdlManager.minimumProtocolVersion = minimumProtocolVersion; + return this; + } + + /** + * The minimum RPC version that will be permitted to connect. + * If the RPC version of the head unit connected is below this version, an UnregisterAppInterface will be sent. + * + * @param minimumRPCVersion the minimum RPC spec version that should be accepted + */ + public Builder setMinimumRPCVersion(final Version minimumRPCVersion) { + sdlManager.minimumRPCVersion = minimumRPCVersion; + return this; + } + + /** + * Sets the Language of the App + * + * @param hmiLanguage the desired language to be used on the display/HMI of the connected module + */ + public Builder setLanguage(final Language hmiLanguage) { + sdlManager.hmiLanguage = hmiLanguage; + sdlManager.language = hmiLanguage; + return this; + } + + /** + * Sets the TemplateColorScheme for daytime + * + * @param dayColorScheme color scheme that will be used (if supported) when the display is in a "Day Mode" or + * similar. Should comprise of colors that contrast well during the day under sunlight. + */ + public Builder setDayColorScheme(final TemplateColorScheme dayColorScheme) { + sdlManager.dayColorScheme = dayColorScheme; + return this; + } + + /** + * Sets the TemplateColorScheme for nighttime + * + * @param nightColorScheme color scheme that will be used (if supported) when the display is in a "Night Mode" + * or similar. Should comprise of colors that contrast well during the night and are not + * brighter than average. + */ + public Builder setNightColorScheme(final TemplateColorScheme nightColorScheme) { + sdlManager.nightColorScheme = nightColorScheme; + return this; + } + + /** + * Sets the icon for the app on head unit / In-Vehicle-Infotainment system <br> + * + * @param sdlArtwork the icon that will be used to represent this application on the connected module + */ + public Builder setAppIcon(final SdlArtwork sdlArtwork) { + sdlManager.appIcon = sdlArtwork; + return this; + } + + /** + * Sets the vector of AppHMIType <br> + * <strong>Note: This should be an ordered list from most -> least relevant</strong> + * + * @param hmiTypes HMI types that represent this application. For example, if the app is a music player, the + * MEDIA HMIType should be included. + */ + public Builder setAppTypes(final Vector<AppHMIType> hmiTypes) { + sdlManager.hmiTypes = hmiTypes; + + if (hmiTypes != null) { + sdlManager.isMediaApp = hmiTypes.contains(AppHMIType.MEDIA); + } + + return this; + } + + /** + * Sets the FileManagerConfig for the session.<br> + * <strong>Note: If not set, the default configuration value of 1 will be set for + * artworkRetryCount and fileRetryCount in FileManagerConfig</strong> + * + * @param fileManagerConfig - configuration options + */ + public Builder setFileManagerConfig(final FileManagerConfig fileManagerConfig) { + sdlManager.fileManagerConfig = fileManagerConfig; + return this; + } + + /** + * Sets the voice recognition synonyms that can be used to identify this application. + * + * @param vrSynonyms a vector of Strings that can be associated with this app. For example the app's name should + * be included as well as any phonetic spellings of the app name that might help the on-board + * VR system associated a users spoken word with the supplied synonyms. + */ + public Builder setVrSynonyms(final Vector<String> vrSynonyms) { + sdlManager.vrSynonyms = vrSynonyms; + return this; + } + + /** + * Sets the Text-To-Speech Name of the application. These TTSChunks might be used by the module as an audio + * representation of the app's name. + * + * @param ttsChunks the TTS chunks that can represent this app's name + */ + public Builder setTtsName(final Vector<TTSChunk> ttsChunks) { + sdlManager.ttsChunks = ttsChunks; + return this; + } + + /** + * This Object type may change with the transport refactor + * Sets the BaseTransportConfig + * + * @param transport the type of transport that should be used for this SdlManager instance. + */ + public Builder setTransportType(@NonNull BaseTransportConfig transport) { + sdlManager.transport = transport; + return this; + } + + /** + * Sets the Security libraries + * + * @param secList The list of security class(es) + */ + @Deprecated + public Builder setSdlSecurity(List<Class<? extends SdlSecurityBase>> secList) { + sdlManager.sdlSecList = secList; + return this; + } + + /** + * Sets the security libraries and a callback to notify caller when there is update to encryption service + * + * @param secList The list of security class(es) + * @param listener The callback object + */ + public Builder setSdlSecurity(@NonNull List<Class<? extends SdlSecurityBase>> secList, ServiceEncryptionListener listener) { + sdlManager.sdlSecList = secList; + sdlManager.serviceEncryptionListener = listener; + return this; + } + + /** + * Set the SdlManager Listener + * + * @param listener the listener + */ + public Builder setManagerListener(@NonNull final SdlManagerListener listener) { + sdlManager.managerListener = listener; + return this; + } + /** + * Set RPCNotification listeners. SdlManager will preload these listeners before any RPCs are sent/received. + * + * @param listeners a map of listeners that will be called when a notification is received. + * Key represents the FunctionID of the notification and value represents the listener + */ + public Builder setRPCNotificationListeners(Map<FunctionID, OnRPCNotificationListener> listeners) { + sdlManager.onRPCNotificationListeners = listeners; + return this; + } + + public SdlManager build() { + if (sdlManager.appName == null) { + throw new IllegalArgumentException("You must specify an app name by calling setAppName"); + } + + if (sdlManager.appId == null) { + throw new IllegalArgumentException("You must specify an app ID by calling setAppId"); + } + + if (sdlManager.managerListener == null) { + throw new IllegalArgumentException("You must set a SdlManagerListener object"); + } + + if (sdlManager.hmiTypes == null) { + Vector<AppHMIType> hmiTypesDefault = new Vector<>(); + hmiTypesDefault.add(AppHMIType.DEFAULT); + sdlManager.hmiTypes = hmiTypesDefault; + sdlManager.isMediaApp = false; + } + if (sdlManager.fileManagerConfig == null) { + //if FileManagerConfig is not set use default + sdlManager.fileManagerConfig = new FileManagerConfig(); + } + + if (sdlManager.hmiLanguage == null) { + sdlManager.hmiLanguage = Language.EN_US; + sdlManager.language = Language.EN_US; + } + + if (sdlManager.minimumProtocolVersion == null) { + sdlManager.minimumProtocolVersion = new Version("1.0.0"); + } + + if (sdlManager.minimumRPCVersion == null) { + sdlManager.minimumRPCVersion = new Version("1.0.0"); + } + + sdlManager.transitionToState(BaseSubManager.SETTING_UP); + + return sdlManager; + } + } + + /** + * Start a secured RPC service + */ + public void startRPCEncryption() { + if (lifecycleManager != null) { + lifecycleManager.startRPCEncryption(); + } + } } diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseEncryptionLifecycleManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseEncryptionLifecycleManager.java index 808f4b0cd..eca2ce239 100644 --- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseEncryptionLifecycleManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseEncryptionLifecycleManager.java @@ -57,6 +57,7 @@ abstract class BaseEncryptionLifecycleManager { private HMILevel currentHMILevel; private Set<String> encryptionRequiredRPCs = new HashSet<>(); private boolean rpcSecuredServiceStarted; + ISdlServiceListener securedServiceListener; BaseEncryptionLifecycleManager(@NonNull ISdl isdl, ServiceEncryptionListener listener) { internalInterface = isdl; @@ -94,7 +95,7 @@ abstract class BaseEncryptionLifecycleManager { } }; - ISdlServiceListener securedServiceListener = new ISdlServiceListener() { + securedServiceListener = new ISdlServiceListener() { @Override public void onServiceStarted(SdlSession session, SessionType type, boolean isEncrypted) { if(SessionType.RPC.equals(type)){ diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java index 254b0d505..0054b7810 100644 --- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java +++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/BaseLifecycleManager.java @@ -32,5 +32,1513 @@ package com.smartdevicelink.managers.lifecycle; +import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; +import android.util.Log; + +import com.smartdevicelink.SdlConnection.ISdlConnectionListener; +import com.smartdevicelink.SdlConnection.SdlSession; +import com.smartdevicelink.exception.SdlException; +import com.smartdevicelink.managers.SdlManager; +import com.smartdevicelink.managers.ServiceEncryptionListener; +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.RPCMessage; +import com.smartdevicelink.proxy.RPCNotification; +import com.smartdevicelink.proxy.RPCRequest; +import com.smartdevicelink.proxy.RPCResponse; +import com.smartdevicelink.proxy.SystemCapabilityManager; +import com.smartdevicelink.proxy.interfaces.IAudioStreamListener; +import com.smartdevicelink.proxy.interfaces.ISdl; +import com.smartdevicelink.proxy.interfaces.ISdlServiceListener; +import com.smartdevicelink.proxy.interfaces.IVideoStreamListener; +import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener; +import com.smartdevicelink.proxy.rpc.OnAppInterfaceUnregistered; +import com.smartdevicelink.proxy.rpc.OnButtonEvent; +import com.smartdevicelink.proxy.rpc.OnButtonPress; +import com.smartdevicelink.proxy.rpc.OnHMIStatus; +import com.smartdevicelink.proxy.rpc.OnSystemRequest; +import com.smartdevicelink.proxy.rpc.RegisterAppInterface; +import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; +import com.smartdevicelink.proxy.rpc.SdlMsgVersion; +import com.smartdevicelink.proxy.rpc.SubscribeButton; +import com.smartdevicelink.proxy.rpc.SystemRequest; +import com.smartdevicelink.proxy.rpc.TTSChunk; +import com.smartdevicelink.proxy.rpc.TemplateColorScheme; +import com.smartdevicelink.proxy.rpc.UnregisterAppInterface; +import com.smartdevicelink.proxy.rpc.VehicleType; +import com.smartdevicelink.proxy.rpc.enums.AppHMIType; +import com.smartdevicelink.proxy.rpc.enums.AppInterfaceUnregisteredReason; +import com.smartdevicelink.proxy.rpc.enums.ButtonName; +import com.smartdevicelink.proxy.rpc.enums.FileType; +import com.smartdevicelink.proxy.rpc.enums.HMILevel; +import com.smartdevicelink.proxy.rpc.enums.Language; +import com.smartdevicelink.proxy.rpc.enums.RequestType; +import com.smartdevicelink.proxy.rpc.enums.Result; +import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason; +import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; +import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; +import com.smartdevicelink.proxy.rpc.listeners.OnPutFileUpdateListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCRequestListener; +import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener; +import com.smartdevicelink.security.SdlSecurityBase; +import com.smartdevicelink.streaming.audio.AudioStreamingCodec; +import com.smartdevicelink.streaming.audio.AudioStreamingParams; +import com.smartdevicelink.streaming.video.VideoStreamingParameters; +import com.smartdevicelink.transport.BaseTransportConfig; +import com.smartdevicelink.util.CorrelationIdGenerator; +import com.smartdevicelink.util.DebugTool; +import com.smartdevicelink.util.FileUtls; +import com.smartdevicelink.util.Version; + +import java.util.HashMap; +import java.util.List; +import java.util.Vector; +import java.util.concurrent.CopyOnWriteArrayList; + abstract class BaseLifecycleManager { + + static final String TAG = "Lifecycle Manager"; + public static final Version MAX_SUPPORTED_RPC_VERSION = new Version(6, 0, 0); + + // Protected Correlation IDs + private final int REGISTER_APP_INTERFACE_CORRELATION_ID = 65529, + UNREGISTER_APP_INTERFACE_CORRELATION_ID = 65530; + + // Sdl Synchronization Objects + private static final Object RPC_LISTENER_LOCK = new Object(), + ON_UPDATE_LISTENER_LOCK = new Object(), + ON_REQUEST_LISTENER_LOCK = new Object(), + ON_NOTIFICATION_LISTENER_LOCK = new Object(); + + SdlSession session; + AppConfig appConfig; + Version rpcSpecVersion = MAX_SUPPORTED_RPC_VERSION; + HashMap<Integer, CopyOnWriteArrayList<OnRPCListener>> rpcListeners; + HashMap<Integer, OnRPCResponseListener> rpcResponseListeners; + HashMap<Integer, CopyOnWriteArrayList<OnRPCNotificationListener>> rpcNotificationListeners; + HashMap<Integer, CopyOnWriteArrayList<OnRPCRequestListener>> rpcRequestListeners; + SystemCapabilityManager systemCapabilityManager; + private EncryptionLifecycleManager encryptionLifecycleManager; + RegisterAppInterfaceResponse raiResponse = null; + private OnHMIStatus currentHMIStatus; + boolean firstTimeFull = true; + LifecycleListener lifecycleListener; + private List<Class<? extends SdlSecurityBase>> _secList = null; + private String authToken; + Version minimumProtocolVersion; + Version minimumRPCVersion; + BaseTransportConfig _transportConfig; + + BaseLifecycleManager(AppConfig appConfig, BaseTransportConfig config, LifecycleListener listener) { + this.appConfig = appConfig; + this._transportConfig = config; + this.lifecycleListener = listener; + this.minimumProtocolVersion = appConfig.getMinimumProtocolVersion(); + this.minimumRPCVersion = appConfig.getMinimumRPCVersion(); + initializeProxy(); + } + + public void start() { + try { + session.startSession(); + } catch (SdlException e) { + e.printStackTrace(); + } + } + + /** + * Start a secured RPC service + */ + public void startRPCEncryption() { + if (session != null) { + session.startService(SessionType.RPC, session.getSessionId(), true); + } + } + + public void stop() { + session.close(); + } + + Version getProtocolVersion() { + if (session != null && session.getProtocolVersion() != null) { + return session.getProtocolVersion(); + } + return new Version(1, 0, 0); + } + + private void sendRPCs(List<? extends RPCMessage> messages, final OnMultipleRequestListener listener) { + if (messages != null) { + for (RPCMessage message : messages) { + // Request Specifics + if (message instanceof RPCRequest) { + RPCRequest request = ((RPCRequest) message); + final OnRPCResponseListener devOnRPCResponseListener = request.getOnRPCResponseListener(); + request.setCorrelationID(CorrelationIdGenerator.generateId()); + if (listener != null) { + listener.addCorrelationId(request.getCorrelationID()); + request.setOnRPCResponseListener(new OnRPCResponseListener() { + @Override + public void onResponse(int correlationId, RPCResponse response) { + if (devOnRPCResponseListener != null) { + devOnRPCResponseListener.onResponse(correlationId, response); + } + if (listener.getSingleRpcResponseListener() != null) { + listener.getSingleRpcResponseListener().onResponse(correlationId, response); + } + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + super.onError(correlationId, resultCode, info); + if (devOnRPCResponseListener != null) { + devOnRPCResponseListener.onError(correlationId, resultCode, info); + } + if (listener.getSingleRpcResponseListener() != null) { + listener.getSingleRpcResponseListener().onError(correlationId, resultCode, info); + } + } + }); + } + sendRPCMessagePrivate(request, false); + } else { + // Notifications and Responses + sendRPCMessagePrivate(message, false); + if (listener != null) { + listener.onUpdate(messages.size()); + if (messages.size() == 0) { + listener.onFinished(); + } + } + } + } + } + } + + private void sendSequentialRPCs(final List<? extends RPCMessage> messages, final OnMultipleRequestListener listener) { + if (messages != null) { + // Break out of recursion, we have finished the requests + if (messages.size() == 0) { + if (listener != null) { + listener.onFinished(); + } + return; + } + + RPCMessage rpc = messages.remove(0); + + // Request Specifics + if (rpc.getMessageType().equals(RPCMessage.KEY_REQUEST)) { + RPCRequest request = (RPCRequest) rpc; + request.setCorrelationID(CorrelationIdGenerator.generateId()); + + final OnRPCResponseListener devOnRPCResponseListener = request.getOnRPCResponseListener(); + + request.setOnRPCResponseListener(new OnRPCResponseListener() { + @Override + public void onResponse(int correlationId, RPCResponse response) { + if (devOnRPCResponseListener != null) { + devOnRPCResponseListener.onResponse(correlationId, response); + } + if (listener != null) { + listener.onResponse(correlationId, response); + listener.onUpdate(messages.size()); + } + // recurse after onResponse + sendSequentialRPCs(messages, listener); + } + + @Override + public void onError(int correlationId, Result resultCode, String info) { + if (devOnRPCResponseListener != null) { + devOnRPCResponseListener.onError(correlationId, resultCode, info); + } + if (listener != null) { + listener.onError(correlationId, resultCode, info); + listener.onUpdate(messages.size()); + + } + // recurse after onError + sendSequentialRPCs(messages, listener); + } + }); + sendRPCMessagePrivate(request, false); + } else { + // Notifications and Responses + sendRPCMessagePrivate(rpc, false); + if (listener != null) { + listener.onUpdate(messages.size()); + } + // recurse after sending a notification or response as there is no response. + sendSequentialRPCs(messages, listener); + } + } + } + + /** + * This method is used to ensure all of the methods in this class can remain private and no grantees can be made + * to the developer what methods are available or not. + * + * <b>NOTE: THERE IS NO GURANTEE THIS WILL BE A VALID SYSTEM CAPABILITY MANAGER</b> + * + * @param sdlManager this must be a working manager instance + * @return the system capability manager. + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + public SystemCapabilityManager getSystemCapabilityManager(SdlManager sdlManager) { + if (sdlManager != null) { + return systemCapabilityManager; + } + return null; + } + + private boolean isConnected() { + if (session != null) { + return session.getIsConnected(); + } else { + return false; + } + } + + /** + * Method to retrieve the RegisterAppInterface Response message that was sent back from the + * module. It contains various attributes about the connected module and can be used to adapt + * to different module types and their supported features. + * + * @return RegisterAppInterfaceResponse received from the module or null if the app has not yet + * registered with the module. + */ + public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() { + return this.raiResponse; + } + + /** + * Get the current OnHMIStatus + * + * @return OnHMIStatus object represents the current OnHMIStatus + */ + public OnHMIStatus getCurrentHMIStatus() { + return currentHMIStatus; + } + + void onClose(String info, Exception e) { + Log.i(TAG, "onClose"); + if (lifecycleListener != null) { + lifecycleListener.onProxyClosed((LifecycleManager) this, info, e, null); + } + } + + /** + * This method is used to ensure all of the methods in this class can remain private and no grantees can be made + * to the developer what methods are available or not. + * + * @param sdlManager this must be a working manager instance + * @return the internal interface that hooks into this manager + */ + @RestrictTo(RestrictTo.Scope.LIBRARY) + public ISdl getInternalInterface(SdlManager sdlManager) { + if (sdlManager != null) { + return internalInterface; + } + return null; + } + + /* ******************************************************************************************************* + ********************************** INTERNAL - RPC LISTENERS !! START !! ********************************* + *********************************************************************************************************/ + + private void setupInternalRpcListeners() { + addRpcListener(FunctionID.REGISTER_APP_INTERFACE, rpcListener); + addRpcListener(FunctionID.ON_HMI_STATUS, rpcListener); + addRpcListener(FunctionID.ON_HASH_CHANGE, rpcListener); + addRpcListener(FunctionID.ON_SYSTEM_REQUEST, rpcListener); + addRpcListener(FunctionID.ON_APP_INTERFACE_UNREGISTERED, rpcListener); + addRpcListener(FunctionID.UNREGISTER_APP_INTERFACE, rpcListener); + } + + private OnRPCListener rpcListener = new OnRPCListener() { + @Override + public void onReceived(RPCMessage message) { + //Make sure this is a response as expected + FunctionID functionID = message.getFunctionID(); + if (functionID != null) { + switch (functionID) { + case REGISTER_APP_INTERFACE: + //We have begun + Log.i(TAG, "RAI Response"); + raiResponse = (RegisterAppInterfaceResponse) message; + SdlMsgVersion rpcVersion = ((RegisterAppInterfaceResponse) message).getSdlMsgVersion(); + if (rpcVersion != null) { + BaseLifecycleManager.this.rpcSpecVersion = new Version(rpcVersion.getMajorVersion(), rpcVersion.getMinorVersion(), rpcVersion.getPatchVersion()); + } else { + BaseLifecycleManager.this.rpcSpecVersion = MAX_SUPPORTED_RPC_VERSION; + } + if (minimumRPCVersion != null && minimumRPCVersion.isNewerThan(rpcSpecVersion) == 1) { + Log.w(TAG, String.format("Disconnecting from head unit, the configured minimum RPC version %s is greater than the supported RPC version %s", minimumRPCVersion, rpcSpecVersion)); + UnregisterAppInterface msg = new UnregisterAppInterface(); + msg.setCorrelationID(UNREGISTER_APP_INTERFACE_CORRELATION_ID); + sendRPCMessagePrivate(msg, true); + cleanProxy(); + return; + } + processRaiResponse(raiResponse); + systemCapabilityManager.parseRAIResponse(raiResponse); + break; + case ON_HMI_STATUS: + Log.i(TAG, "on hmi status"); + boolean shouldInit = currentHMIStatus == null; + currentHMIStatus = (OnHMIStatus) message; + if (lifecycleListener != null && shouldInit) { + lifecycleListener.onProxyConnected((LifecycleManager) BaseLifecycleManager.this); + } + break; + case ON_HASH_CHANGE: + break; + case ON_SYSTEM_REQUEST: + final OnSystemRequest onSystemRequest = (OnSystemRequest) message; + if ((onSystemRequest.getUrl() != null) && + (((onSystemRequest.getRequestType() == RequestType.PROPRIETARY) && (onSystemRequest.getFileType() == FileType.JSON)) + || ((onSystemRequest.getRequestType() == RequestType.HTTP) && (onSystemRequest.getFileType() == FileType.BINARY)))) { + Thread handleOffboardTransmissionThread = new Thread() { + @Override + public void run() { + RPCRequest request = PoliciesFetcher.fetchPolicies(onSystemRequest); + if (request != null && isConnected()) { + sendRPCMessagePrivate(request, true); + } + } + }; + handleOffboardTransmissionThread.start(); + } else if (onSystemRequest.getRequestType() == RequestType.ICON_URL && onSystemRequest.getUrl() != null) { + //Download the icon file and send SystemRequest RPC + Thread handleOffBoardTransmissionThread = new Thread() { + @Override + public void run() { + final String urlHttps = onSystemRequest.getUrl().replaceFirst("http://", "https://"); + byte[] file = FileUtls.downloadFile(urlHttps); + if (file != null) { + SystemRequest systemRequest = new SystemRequest(); + systemRequest.setFileName(onSystemRequest.getUrl()); + systemRequest.setBulkData(file); + systemRequest.setRequestType(RequestType.ICON_URL); + if (isConnected()) { + sendRPCMessagePrivate(systemRequest, true); + } + } else { + DebugTool.logError("File was null at: " + urlHttps); + } + } + }; + handleOffBoardTransmissionThread.start(); + } + break; + case ON_APP_INTERFACE_UNREGISTERED: + + OnAppInterfaceUnregistered onAppInterfaceUnregistered = (OnAppInterfaceUnregistered) message; + + if (!onAppInterfaceUnregistered.getReason().equals(AppInterfaceUnregisteredReason.LANGUAGE_CHANGE)) { + Log.v(TAG, "on app interface unregistered"); + cleanProxy(); + } else { + Log.v(TAG, "re-registering for language change"); + processLanguageChange(); + } + break; + case UNREGISTER_APP_INTERFACE: + Log.v(TAG, "unregister app interface"); + cleanProxy(); + break; + } + } + } + + + }; + + private void processLanguageChange() { + if (session != null) { + if (session.getIsConnected()) { + session.close(); + } + try { + session.startSession(); + } catch (SdlException e) { + e.printStackTrace(); + } + } + } + + /* ******************************************************************************************************* + ********************************** INTERNAL - RPC LISTENERS !! END !! ********************************* + *********************************************************************************************************/ + + + /* ******************************************************************************************************* + ********************************** METHODS - RPC LISTENERS !! START !! ********************************** + *********************************************************************************************************/ + + private boolean onRPCReceived(final RPCMessage message) { + synchronized (RPC_LISTENER_LOCK) { + if (message == null || message.getFunctionID() == null) { + return false; + } + + final int id = message.getFunctionID().getId(); + CopyOnWriteArrayList<OnRPCListener> listeners = rpcListeners.get(id); + if (listeners != null && listeners.size() > 0) { + for (OnRPCListener listener : listeners) { + listener.onReceived(message); + } + return true; + } + return false; + } + } + + private void addRpcListener(FunctionID id, OnRPCListener listener) { + synchronized (RPC_LISTENER_LOCK) { + if (id != null && listener != null) { + if (!rpcListeners.containsKey(id.getId())) { + rpcListeners.put(id.getId(), new CopyOnWriteArrayList<OnRPCListener>()); + } + + rpcListeners.get(id.getId()).add(listener); + } + } + } + + private boolean removeOnRPCListener(FunctionID id, OnRPCListener listener) { + synchronized (RPC_LISTENER_LOCK) { + if (rpcListeners != null + && id != null + && listener != null + && rpcListeners.containsKey(id.getId())) { + return rpcListeners.get(id.getId()).remove(listener); + } + } + return false; + } + + /** + * Only call this method for a PutFile response. It will cause a class cast exception if not. + * + * @param correlationId correlation id of the packet being updated + * @param bytesWritten how many bytes were written + * @param totalSize the total size in bytes + */ + @SuppressWarnings("unused") + private void onPacketProgress(int correlationId, long bytesWritten, long totalSize) { + synchronized (ON_UPDATE_LISTENER_LOCK) { + if (rpcResponseListeners != null + && rpcResponseListeners.containsKey(correlationId)) { + ((OnPutFileUpdateListener) rpcResponseListeners.get(correlationId)).onUpdate(correlationId, bytesWritten, totalSize); + } + } + + } + + /** + * Will provide callback to the listener either onFinish or onError depending on the RPCResponses result code, + * <p>Will automatically remove the listener for the list of listeners on completion. + * + * @param msg The RPCResponse message that was received + * @return if a listener was called or not + */ + @SuppressWarnings("UnusedReturnValue") + private boolean onRPCResponseReceived(RPCResponse msg) { + synchronized (ON_UPDATE_LISTENER_LOCK) { + int correlationId = msg.getCorrelationID(); + if (rpcResponseListeners != null + && rpcResponseListeners.containsKey(correlationId)) { + OnRPCResponseListener listener = rpcResponseListeners.get(correlationId); + if (msg.getSuccess()) { + listener.onResponse(correlationId, msg); + } else { + listener.onError(correlationId, msg.getResultCode(), msg.getInfo()); + } + rpcResponseListeners.remove(correlationId); + return true; + } + return false; + } + } + + /** + * Add a listener that will receive the response to the specific RPCRequest sent with the corresponding correlation id + * + * @param listener that will get called back when a response is received + * @param correlationId of the RPCRequest that was sent + * @param totalSize only include if this is an OnPutFileUpdateListener. Otherwise it will be ignored. + */ + private void addOnRPCResponseListener(OnRPCResponseListener listener, int correlationId, int totalSize) { + synchronized (ON_UPDATE_LISTENER_LOCK) { + if (rpcResponseListeners != null + && listener != null) { + if (listener.getListenerType() == OnRPCResponseListener.UPDATE_LISTENER_TYPE_PUT_FILE) { + ((OnPutFileUpdateListener) listener).setTotalSize(totalSize); + } + listener.onStart(correlationId); + rpcResponseListeners.put(correlationId, listener); + } + } + } + + @SuppressWarnings("unused") + private HashMap<Integer, OnRPCResponseListener> getResponseListeners() { + synchronized (ON_UPDATE_LISTENER_LOCK) { + return this.rpcResponseListeners; + } + } + + /** + * Retrieves the auth token, if any, that was attached to the StartServiceACK for the RPC + * service from the module. For example, this should be used to login to a user account. + * + * @return the string representation of the auth token + */ + public String getAuthToken() { + return this.authToken; + } + + @SuppressWarnings("UnusedReturnValue") + private boolean onRPCNotificationReceived(RPCNotification notification) { + if (notification == null) { + DebugTool.logError("onRPCNotificationReceived - Notification was null"); + return false; + } + DebugTool.logInfo("onRPCNotificationReceived - " + notification.getFunctionName()); + + //Before updating any listeners, make sure to do any final updates to the notification RPC now + if (FunctionID.ON_HMI_STATUS.toString().equals(notification.getFunctionName())) { + OnHMIStatus onHMIStatus = (OnHMIStatus) notification; + onHMIStatus.setFirstRun(firstTimeFull); + if (onHMIStatus.getHmiLevel() == HMILevel.HMI_FULL) { + firstTimeFull = false; + } + } + + synchronized (ON_NOTIFICATION_LISTENER_LOCK) { + CopyOnWriteArrayList<OnRPCNotificationListener> listeners = rpcNotificationListeners.get(FunctionID.getFunctionId(notification.getFunctionName())); + if (listeners != null && listeners.size() > 0) { + for (OnRPCNotificationListener listener : listeners) { + listener.onNotified(notification); + } + return true; + } + return false; + } + } + + /** + * This will add a listener for the specific type of notification. As of now it will only allow + * a single listener per notification function id + * + * @param notificationId The notification type that this listener is designated for + * @param listener The listener that will be called when a notification of the provided type is received + */ + @SuppressWarnings("unused") + private void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + synchronized (ON_NOTIFICATION_LISTENER_LOCK) { + if (notificationId != null && listener != null) { + if (!rpcNotificationListeners.containsKey(notificationId.getId())) { + rpcNotificationListeners.put(notificationId.getId(), new CopyOnWriteArrayList<OnRPCNotificationListener>()); + } + rpcNotificationListeners.get(notificationId.getId()).add(listener); + } + } + } + + private boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + synchronized (ON_NOTIFICATION_LISTENER_LOCK) { + if (rpcNotificationListeners != null + && notificationId != null + && listener != null + && rpcNotificationListeners.containsKey(notificationId.getId())) { + return rpcNotificationListeners.get(notificationId.getId()).remove(listener); + } + } + return false; + } + + @SuppressWarnings("UnusedReturnValue") + private boolean onRPCRequestReceived(RPCRequest request) { + if (request == null) { + DebugTool.logError("onRPCRequestReceived - request was null"); + return false; + } + DebugTool.logInfo("onRPCRequestReceived - " + request.getFunctionName()); + + synchronized (ON_REQUEST_LISTENER_LOCK) { + CopyOnWriteArrayList<OnRPCRequestListener> listeners = rpcRequestListeners.get(FunctionID.getFunctionId(request.getFunctionName())); + if (listeners != null && listeners.size() > 0) { + for (OnRPCRequestListener listener : listeners) { + listener.onRequest(request); + } + return true; + } + return false; + } + } + + /** + * This will add a listener for the specific type of request. As of now it will only allow + * a single listener per request function id + * + * @param requestId The request type that this listener is designated for + * @param listener The listener that will be called when a request of the provided type is received + */ + @SuppressWarnings("unused") + private void addOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener) { + synchronized (ON_REQUEST_LISTENER_LOCK) { + if (requestId != null && listener != null) { + if (!rpcRequestListeners.containsKey(requestId.getId())) { + rpcRequestListeners.put(requestId.getId(), new CopyOnWriteArrayList<OnRPCRequestListener>()); + } + rpcRequestListeners.get(requestId.getId()).add(listener); + } + } + } + + @SuppressWarnings("UnusedReturnValue") + private boolean removeOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener) { + synchronized (ON_REQUEST_LISTENER_LOCK) { + if (rpcRequestListeners != null + && requestId != null + && listener != null + && rpcRequestListeners.containsKey(requestId.getId())) { + return rpcRequestListeners.get(requestId.getId()).remove(listener); + } + } + return false; + } + + /* ******************************************************************************************************* + **************************************** RPC LISTENERS !! END !! **************************************** + *********************************************************************************************************/ + + private void sendRPCMessagePrivate(RPCMessage message, boolean isInternalMessage) { + try { + if (!isInternalMessage && message.getMessageType().equals(RPCMessage.KEY_REQUEST)) { + RPCRequest request = (RPCRequest) message; + OnRPCResponseListener listener = request.getOnRPCResponseListener(); + + // Test for illegal correlation ID + if (request.getCorrelationID() == REGISTER_APP_INTERFACE_CORRELATION_ID || request.getCorrelationID() == UNREGISTER_APP_INTERFACE_CORRELATION_ID || request.getCorrelationID() == PoliciesFetcher.POLICIES_CORRELATION_ID) { + if (listener != null) { + request.getOnRPCResponseListener().onError(request.getCorrelationID(), Result.REJECTED, "Invalid correlation ID. The correlation ID, " + request.getCorrelationID() + " , is a reserved correlation ID."); + } + return; + } + + // Prevent developer from sending RAI or UAI manually + if (request.getFunctionName().equals(FunctionID.REGISTER_APP_INTERFACE.toString()) || request.getFunctionName().equals(FunctionID.UNREGISTER_APP_INTERFACE.toString())) { + if (listener != null) { + request.getOnRPCResponseListener().onError(request.getCorrelationID(), Result.REJECTED, "The RPCRequest, " + message.getFunctionName() + ", is un-allowed to be sent manually by the developer."); + } + return; + } + } + + //FIXME this is temporary until the next major release of the library where OK is removed + if (message.getMessageType().equals(RPCMessage.KEY_REQUEST)) { + RPCRequest request = (RPCRequest) message; + if (FunctionID.SUBSCRIBE_BUTTON.toString().equals(request.getFunctionName()) + || FunctionID.UNSUBSCRIBE_BUTTON.toString().equals(request.getFunctionName()) + || FunctionID.BUTTON_PRESS.toString().equals(request.getFunctionName())) { + + ButtonName buttonName = (ButtonName) request.getObject(ButtonName.class, SubscribeButton.KEY_BUTTON_NAME); + + + if (rpcSpecVersion != null) { + if (rpcSpecVersion.getMajor() < 5) { + + if (ButtonName.PLAY_PAUSE.equals(buttonName)) { + request.setParameters(SubscribeButton.KEY_BUTTON_NAME, ButtonName.OK); + } + } else { //Newer than version 5.0.0 + if (ButtonName.OK.equals(buttonName)) { + RPCRequest request2 = new RPCRequest(request); + request2.setParameters(SubscribeButton.KEY_BUTTON_NAME, ButtonName.PLAY_PAUSE); + request2.setOnRPCResponseListener(request.getOnRPCResponseListener()); + sendRPCMessagePrivate(request2, true); + return; + } + } + } + + } + } + + message.format(rpcSpecVersion, true); + byte[] msgBytes = JsonRPCMarshaller.marshall(message, (byte) getProtocolVersion().getMajor()); + + final ProtocolMessage pm = new ProtocolMessage(); + pm.setData(msgBytes); + if (session != null) { + pm.setSessionID(session.getSessionId()); + } + + pm.setMessageType(MessageType.RPC); + pm.setSessionType(SessionType.RPC); + pm.setFunctionID(FunctionID.getFunctionId(message.getFunctionName())); + + if (encryptionLifecycleManager != null && encryptionLifecycleManager.isEncryptionReady() && encryptionLifecycleManager.getRPCRequiresEncryption(message.getFunctionID())) { + pm.setPayloadProtected(true); + } else { + pm.setPayloadProtected(message.isPayloadProtected()); + } + if (pm.getPayloadProtected() && (encryptionLifecycleManager == null || !encryptionLifecycleManager.isEncryptionReady())) { + String errorInfo = "Trying to send an encrypted message and there is no secured service"; + if (message.getMessageType().equals((RPCMessage.KEY_REQUEST))) { + RPCRequest request = (RPCRequest) message; + OnRPCResponseListener listener = ((RPCRequest) message).getOnRPCResponseListener(); + if (listener != null) { + listener.onError(request.getCorrelationID(), Result.ABORTED, errorInfo); + } + } + DebugTool.logWarning(errorInfo); + return; + } + + if (RPCMessage.KEY_REQUEST.equals(message.getMessageType())) { // Request Specifics + pm.setRPCType((byte) 0x00); + Integer corrId = ((RPCRequest) message).getCorrelationID(); + if (corrId == null) { + Log.e(TAG, "No correlation ID attached to request. Not sending"); + return; + } else { + pm.setCorrID(corrId); + + OnRPCResponseListener listener = ((RPCRequest) message).getOnRPCResponseListener(); + if (listener != null) { + addOnRPCResponseListener(listener, corrId, msgBytes.length); + } + } + } else if (RPCMessage.KEY_RESPONSE.equals(message.getMessageType())) { // Response Specifics + RPCResponse response = (RPCResponse) message; + pm.setRPCType((byte) 0x01); + if (response.getCorrelationID() == null) { + //Log error here + //throw new SdlException("CorrelationID cannot be null. RPC: " + response.getFunctionName(), SdlExceptionCause.INVALID_ARGUMENT); + Log.e(TAG, "No correlation ID attached to response. Not sending"); + return; + } else { + pm.setCorrID(response.getCorrelationID()); + } + } else if (message.getMessageType().equals(RPCMessage.KEY_NOTIFICATION)) { // Notification Specifics + pm.setRPCType((byte) 0x02); + } + + if (message.getBulkData() != null) { + pm.setBulkData(message.getBulkData()); + } + + if (message.getFunctionName().equalsIgnoreCase(FunctionID.PUT_FILE.name())) { + pm.setPriorityCoefficient(1); + } + + session.sendMessage(pm); + + } catch (OutOfMemoryError e) { + e.printStackTrace(); + } + } + + /* ******************************************************************************************************* + *************************************** ISdlConnectionListener START ************************************ + *********************************************************************************************************/ + + final ISdlConnectionListener sdlConnectionListener = new ISdlConnectionListener() { + @Override + public void onTransportDisconnected(String info) { + onClose(info, null); + + } + + @Override + public void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig) { + BaseLifecycleManager.this.onTransportDisconnected(info, availablePrimary, transportConfig); + + } + + @Override + public void onTransportError(String info, Exception e) { + onClose(info, e); + + } + + @Override + public void onProtocolMessageReceived(ProtocolMessage msg) { + //Incoming message + if (SessionType.RPC.equals(msg.getSessionType()) + || SessionType.BULK_DATA.equals(msg.getSessionType())) { + + RPCMessage rpc = RpcConverter.extractRpc(msg, session.getProtocolVersion()); + if (rpc != null) { + String messageType = rpc.getMessageType(); + Log.v(TAG, "RPC received - " + messageType); + + rpc.format(rpcSpecVersion, true); + + onRPCReceived(rpc); + + if (RPCMessage.KEY_RESPONSE.equals(messageType)) { + + onRPCResponseReceived((RPCResponse) rpc); + + } else if (RPCMessage.KEY_NOTIFICATION.equals(messageType)) { + FunctionID functionID = rpc.getFunctionID(); + if (functionID != null && (functionID.equals(FunctionID.ON_BUTTON_PRESS)) || functionID.equals(FunctionID.ON_BUTTON_EVENT)) { + RPCNotification notificationCompat = handleButtonNotificationFormatting(rpc); + if (notificationCompat != null) { + onRPCNotificationReceived((notificationCompat)); + } + } + + onRPCNotificationReceived((RPCNotification) rpc); + + } else if (RPCMessage.KEY_REQUEST.equals(messageType)) { + + onRPCRequestReceived((RPCRequest) rpc); + + } + } else { + Log.w(TAG, "Shouldn't be here"); + } + } + + } + + @Override + public void onProtocolSessionStartedNACKed(SessionType sessionType, byte sessionID, byte version, String correlationID, List<String> rejectedParams) { + Log.w(TAG, sessionType + " onProtocolSessionStartedNACKed " + sessionID + " RejectedParams: " + rejectedParams); + BaseLifecycleManager.this.onProtocolSessionStartedNACKed(sessionType); + } + + @Override + public void onProtocolSessionStarted(SessionType sessionType, byte sessionID, byte version, String correlationID, int hashID, boolean isEncrypted) { + Log.i(TAG, "on protocol session started"); + BaseLifecycleManager.this.onProtocolSessionStarted(sessionType); + } + + @Override + public void onProtocolSessionEnded(SessionType sessionType, byte sessionID, String correlationID) { + BaseLifecycleManager.this.onProtocolSessionEnded(sessionType); + } + + @Override + public void onProtocolSessionEndedNACKed(SessionType sessionType, byte sessionID, String correlationID) { + BaseLifecycleManager.this.onProtocolSessionEndedNACKed(sessionType); + } + + @Override + public void onProtocolError(String info, Exception e) { + DebugTool.logError("Protocol Error - " + info, e); + } + + @Override + public void onHeartbeatTimedOut(byte sessionID) { /* Deprecated */ } + + @Override + public void onProtocolServiceDataACK(SessionType sessionType, int dataSize, byte sessionID) {/* Unused */ } + + + @Override + public void onAuthTokenReceived(String token, byte sessionID) { + BaseLifecycleManager.this.authToken = token; + } + }; + + /* ******************************************************************************************************* + *************************************** ISdlConnectionListener END ************************************ + *********************************************************************************************************/ + + + /* ******************************************************************************************************* + ******************************************** ISdl - START *********************************************** + *********************************************************************************************************/ + + final ISdl internalInterface = new ISdl() { + @Override + public void start() { + BaseLifecycleManager.this.start(); + } + + @Override + public void stop() { + BaseLifecycleManager.this.stop(); + } + + @Override + public boolean isConnected() { + return BaseLifecycleManager.this.session.getIsConnected(); + } + + @Override + public void addServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { + BaseLifecycleManager.this.session.addServiceListener(serviceType, sdlServiceListener); + } + + @Override + public void removeServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { + BaseLifecycleManager.this.session.removeServiceListener(serviceType, sdlServiceListener); + } + + @Override + public void startVideoService(VideoStreamingParameters parameters, boolean encrypted) { + BaseLifecycleManager.this.startVideoService(encrypted, parameters); + } + + @Override + public void stopVideoService() { + BaseLifecycleManager.this.endVideoStream(); + } + + @Override + public IVideoStreamListener startVideoStream(boolean isEncrypted, VideoStreamingParameters parameters) { + DebugTool.logWarning("startVideoStream is not currently implemented"); + return null; + } + + @Override + public void startAudioService(boolean encrypted, AudioStreamingCodec codec, AudioStreamingParams params) { + DebugTool.logWarning("startAudioService is not currently implemented"); + } + + @Override + public void startAudioService(boolean encrypted) { + BaseLifecycleManager.this.startAudioService(encrypted); + } + + @Override + public void stopAudioService() { + BaseLifecycleManager.this.endAudioStream(); + } + + @Override + public IAudioStreamListener startAudioStream(boolean isEncrypted, AudioStreamingCodec codec, AudioStreamingParams params) { + DebugTool.logWarning("startAudioStream is not currently implemented"); + return null; + } + + @Override + public void sendRPCRequest(RPCRequest message) { + BaseLifecycleManager.this.sendRPCMessagePrivate(message, false); + } + + @Override + public void sendRPC(RPCMessage message) { + if (isConnected()) { + BaseLifecycleManager.this.sendRPCMessagePrivate(message, false); + } + } + + @Override + public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { + BaseLifecycleManager.this.sendRPCs(rpcs, listener); + } + + @Override + public void sendRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { + BaseLifecycleManager.this.sendRPCs(rpcs, listener); + } + + @Override + public void sendSequentialRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { + BaseLifecycleManager.this.sendSequentialRPCs(rpcs, listener); + } + + @Override + public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + BaseLifecycleManager.this.addOnRPCNotificationListener(notificationId, listener); + } + + @Override + public boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { + return BaseLifecycleManager.this.removeOnRPCNotificationListener(notificationId, listener); + } + + @Override + public void addOnRPCRequestListener(FunctionID notificationId, OnRPCRequestListener listener) { + BaseLifecycleManager.this.addOnRPCRequestListener(notificationId, listener); + } + + @Override + public boolean removeOnRPCRequestListener(FunctionID notificationId, OnRPCRequestListener listener) { + return BaseLifecycleManager.this.removeOnRPCRequestListener(notificationId, listener); + } + + @Override + public void addOnRPCListener(FunctionID responseId, OnRPCListener listener) { + BaseLifecycleManager.this.addRpcListener(responseId, listener); + } + + @Override + public boolean removeOnRPCListener(FunctionID responseId, OnRPCListener listener) { + return BaseLifecycleManager.this.removeOnRPCListener(responseId, listener); + } + + @Override + public Object getCapability(SystemCapabilityType systemCapabilityType) { + if (BaseLifecycleManager.this.systemCapabilityManager != null) { + return BaseLifecycleManager.this.systemCapabilityManager.getCapability(systemCapabilityType); + } else { + return null; + } + } + + @Override + public void getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener) { + if (BaseLifecycleManager.this.systemCapabilityManager != null) { + BaseLifecycleManager.this.systemCapabilityManager.getCapability(systemCapabilityType, scListener); + } + } + + @Override + public Object getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener, boolean forceUpdate) { + if (BaseLifecycleManager.this.systemCapabilityManager != null) { + return BaseLifecycleManager.this.systemCapabilityManager.getCapability(systemCapabilityType, scListener, forceUpdate); + } else { + return null; + } + } + + @Override + public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() { + return raiResponse; + } + + @Override + public boolean isCapabilitySupported(SystemCapabilityType systemCapabilityType) { + if (BaseLifecycleManager.this.systemCapabilityManager != null) { + return BaseLifecycleManager.this.systemCapabilityManager.isCapabilitySupported(systemCapabilityType); + } else { + return false; + } + } + + @Override + public void addOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener) { + if (BaseLifecycleManager.this.systemCapabilityManager != null) { + BaseLifecycleManager.this.systemCapabilityManager.addOnSystemCapabilityListener(systemCapabilityType, listener); + } + } + + @Override + public boolean removeOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener) { + if (BaseLifecycleManager.this.systemCapabilityManager != null) { + return BaseLifecycleManager.this.systemCapabilityManager.removeOnSystemCapabilityListener(systemCapabilityType, listener); + } else { + return false; + } + } + + @Override + public boolean isTransportForServiceAvailable(SessionType serviceType) { + return BaseLifecycleManager.this.session.isTransportForServiceAvailable(serviceType); + } + + @Override + public SdlMsgVersion getSdlMsgVersion() { + SdlMsgVersion msgVersion = new SdlMsgVersion(rpcSpecVersion.getMajor(), rpcSpecVersion.getMinor()); + msgVersion.setPatchVersion(rpcSpecVersion.getPatch()); + return msgVersion; + } + + @Override + public Version getProtocolVersion() { + return BaseLifecycleManager.this.getProtocolVersion(); + } + + @Override + public void startRPCEncryption() { + BaseLifecycleManager.this.startRPCEncryption(); + } + }; + + /* ******************************************************************************************************* + ********************************************* ISdl - END ************************************************ + *********************************************************************************************************/ + + public interface LifecycleListener { + void onProxyConnected(LifecycleManager lifeCycleManager); + + void onProxyClosed(LifecycleManager lifeCycleManager, String info, Exception e, SdlDisconnectedReason reason); + + void onServiceStarted(SessionType sessionType); + + void onServiceEnded(SessionType sessionType); + + void onError(LifecycleManager lifeCycleManager, String info, Exception e); + } + + public static class AppConfig { + private String appID, appName, ngnMediaScreenAppName; + private Vector<TTSChunk> ttsName; + private Vector<String> vrSynonyms; + private boolean isMediaApp = false; + private Language languageDesired, hmiDisplayLanguageDesired; + private Vector<AppHMIType> appType; + private TemplateColorScheme dayColorScheme, nightColorScheme; + private Version minimumProtocolVersion; + private Version minimumRPCVersion; + + private void prepare() { + if (getNgnMediaScreenAppName() == null) { + setNgnMediaScreenAppName(getAppName()); + } + + if (getLanguageDesired() == null) { + setLanguageDesired(Language.EN_US); + } + + if (getHmiDisplayLanguageDesired() == null) { + setHmiDisplayLanguageDesired(Language.EN_US); + } + + if (getVrSynonyms() == null) { + setVrSynonyms(new Vector<String>()); + getVrSynonyms().add(getAppName()); + } + } + + public String getAppID() { + return appID; + } + + public void setAppID(String appID) { + this.appID = appID; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getNgnMediaScreenAppName() { + return ngnMediaScreenAppName; + } + + public void setNgnMediaScreenAppName(String ngnMediaScreenAppName) { + this.ngnMediaScreenAppName = ngnMediaScreenAppName; + } + + public Vector<TTSChunk> getTtsName() { + return ttsName; + } + + public void setTtsName(Vector<TTSChunk> ttsName) { + this.ttsName = ttsName; + } + + public Vector<String> getVrSynonyms() { + return vrSynonyms; + } + + public void setVrSynonyms(Vector<String> vrSynonyms) { + this.vrSynonyms = vrSynonyms; + } + + public boolean isMediaApp() { + return isMediaApp; + } + + public void setMediaApp(boolean mediaApp) { + isMediaApp = mediaApp; + } + + public Language getLanguageDesired() { + return languageDesired; + } + + public void setLanguageDesired(Language languageDesired) { + this.languageDesired = languageDesired; + } + + public Language getHmiDisplayLanguageDesired() { + return hmiDisplayLanguageDesired; + } + + public void setHmiDisplayLanguageDesired(Language hmiDisplayLanguageDesired) { + this.hmiDisplayLanguageDesired = hmiDisplayLanguageDesired; + } + + public Vector<AppHMIType> getAppType() { + return appType; + } + + public void setAppType(Vector<AppHMIType> appType) { + this.appType = appType; + } + + public TemplateColorScheme getDayColorScheme() { + return dayColorScheme; + } + + public void setDayColorScheme(TemplateColorScheme dayColorScheme) { + this.dayColorScheme = dayColorScheme; + } + + public TemplateColorScheme getNightColorScheme() { + return nightColorScheme; + } + + public void setNightColorScheme(TemplateColorScheme nightColorScheme) { + this.nightColorScheme = nightColorScheme; + } + + public Version getMinimumProtocolVersion() { + return minimumProtocolVersion; + } + + /** + * Sets the minimum protocol version that will be permitted to connect. + * If the protocol version of the head unit connected is below this version, + * the app will disconnect with an EndService protocol message and will not register. + * + * @param minimumProtocolVersion a Version object with the minimally accepted Protocol version + */ + public void setMinimumProtocolVersion(Version minimumProtocolVersion) { + this.minimumProtocolVersion = minimumProtocolVersion; + } + + public Version getMinimumRPCVersion() { + return minimumRPCVersion; + } + + /** + * The minimum RPC version that will be permitted to connect. + * If the RPC version of the head unit connected is below this version, an UnregisterAppInterface will be sent. + * + * @param minimumRPCVersion a Version object with the minimally accepted RPC spec version + */ + public void setMinimumRPCVersion(Version minimumRPCVersion) { + this.minimumRPCVersion = minimumRPCVersion; + } + } + + /** + * Temporary method to bridge the new PLAY_PAUSE and OKAY button functionality with the old + * OK button name. This should be removed during the next major release + * + * @param notification an RPC message object that should be either an ON_BUTTON_EVENT or ON_BUTTON_PRESS otherwise + * it will be ignored + */ + private RPCNotification handleButtonNotificationFormatting(RPCMessage notification) { + if (FunctionID.ON_BUTTON_EVENT.toString().equals(notification.getFunctionName()) + || FunctionID.ON_BUTTON_PRESS.toString().equals(notification.getFunctionName())) { + + ButtonName buttonName = (ButtonName) notification.getObject(ButtonName.class, OnButtonEvent.KEY_BUTTON_NAME); + ButtonName compatBtnName = null; + + if (rpcSpecVersion != null && rpcSpecVersion.getMajor() >= 5) { + if (ButtonName.PLAY_PAUSE.equals(buttonName)) { + compatBtnName = ButtonName.OK; + } + } else { // rpc spec version is either null or less than 5 + if (ButtonName.OK.equals(buttonName)) { + compatBtnName = ButtonName.PLAY_PAUSE; + } + } + + try { + if (compatBtnName != null) { //There is a button name that needs to be swapped out + RPCNotification notification2; + //The following is done because there is currently no way to make a deep copy + //of an RPC. Since this code will be removed, it's ugliness is borderline acceptable. + if (notification instanceof OnButtonEvent) { + OnButtonEvent onButtonEvent = new OnButtonEvent(); + onButtonEvent.setButtonEventMode(((OnButtonEvent) notification).getButtonEventMode()); + onButtonEvent.setCustomButtonID(((OnButtonEvent) notification).getCustomButtonID()); + notification2 = onButtonEvent; + } else if (notification instanceof OnButtonPress) { + OnButtonPress onButtonPress = new OnButtonPress(); + onButtonPress.setButtonPressMode(((OnButtonPress) notification).getButtonPressMode()); + onButtonPress.setCustomButtonName(((OnButtonPress) notification).getCustomButtonName()); + notification2 = onButtonPress; + } else { + return null; + } + + notification2.setParameters(OnButtonEvent.KEY_BUTTON_NAME, compatBtnName); + return notification2; + } + } catch (Exception e) { + //Should never get here + } + } + return null; + } + + void cleanProxy() { + firstTimeFull = true; + currentHMIStatus = null; + if (rpcListeners != null) { + rpcListeners.clear(); + } + if (rpcResponseListeners != null) { + rpcResponseListeners.clear(); + } + if (rpcNotificationListeners != null) { + rpcNotificationListeners.clear(); + } + if (rpcRequestListeners != null) { + rpcRequestListeners.clear(); + } + if (session != null && session.getIsConnected()) { + session.close(); + } + if (encryptionLifecycleManager != null) { + encryptionLifecycleManager.dispose(); + } + } + + @Deprecated + public void setSdlSecurityClassList(List<Class<? extends SdlSecurityBase>> list) { + _secList = list; + } + + /** + * Sets the security libraries and a callback to notify caller when there is update to encryption service + * + * @param secList The list of security class(es) + * @param listener The callback object + */ + public void setSdlSecurity(@NonNull List<Class<? extends SdlSecurityBase>> secList, ServiceEncryptionListener listener) { + this._secList = secList; + this.encryptionLifecycleManager = new EncryptionLifecycleManager(internalInterface, listener); + } + + private void processRaiResponse(RegisterAppInterfaceResponse rai) { + if (rai == null) return; + + this.raiResponse = rai; + + VehicleType vt = rai.getVehicleType(); + if (vt == null) return; + + String make = vt.getMake(); + if (make == null) return; + + if (_secList == null) return; + + setSdlSecurityStaticVars(); + + SdlSecurityBase sec; + + for (Class<? extends SdlSecurityBase> cls : _secList) { + try { + sec = cls.newInstance(); + } catch (Exception e) { + continue; + } + + if ((sec != null) && (sec.getMakeList() != null)) { + if (sec.getMakeList().contains(make)) { + sec.setAppId(appConfig.getAppID()); + if (session != null) { + session.setSdlSecurity(sec); + sec.handleSdlSession(session); + } + return; + } + } + } + } + + /* ******************************************************************************************************* + ********************************** Platform specific methods - START ************************************* + *********************************************************************************************************/ + + void initializeProxy() { + this.rpcListeners = new HashMap<>(); + this.rpcResponseListeners = new HashMap<>(); + this.rpcNotificationListeners = new HashMap<>(); + this.rpcRequestListeners = new HashMap<>(); + this.systemCapabilityManager = new SystemCapabilityManager(internalInterface); + setupInternalRpcListeners(); + } + + void onProtocolSessionStarted(SessionType sessionType) { + if (sessionType != null) { + if (minimumProtocolVersion != null && minimumProtocolVersion.isNewerThan(getProtocolVersion()) == 1) { + Log.w(TAG, String.format("Disconnecting from head unit, the configured minimum protocol version %s is greater than the supported protocol version %s", minimumProtocolVersion, getProtocolVersion())); + session.endService(sessionType, session.getSessionId()); + cleanProxy(); + return; + } + + if (sessionType.equals(SessionType.RPC)) { + if (appConfig != null) { + + appConfig.prepare(); + + SdlMsgVersion sdlMsgVersion = new SdlMsgVersion(); + sdlMsgVersion.setMajorVersion(MAX_SUPPORTED_RPC_VERSION.getMajor()); + sdlMsgVersion.setMinorVersion(MAX_SUPPORTED_RPC_VERSION.getMinor()); + sdlMsgVersion.setPatchVersion(MAX_SUPPORTED_RPC_VERSION.getPatch()); + + RegisterAppInterface rai = new RegisterAppInterface(sdlMsgVersion, + appConfig.getAppName(), appConfig.isMediaApp(), appConfig.getLanguageDesired(), + appConfig.getHmiDisplayLanguageDesired(), appConfig.getAppID()); + rai.setCorrelationID(REGISTER_APP_INTERFACE_CORRELATION_ID); + + rai.setTtsName(appConfig.getTtsName()); + rai.setNgnMediaScreenAppName(appConfig.getNgnMediaScreenAppName()); + rai.setVrSynonyms(appConfig.getVrSynonyms()); + rai.setAppHMIType(appConfig.getAppType()); + rai.setDayColorScheme(appConfig.getDayColorScheme()); + rai.setNightColorScheme(appConfig.getNightColorScheme()); + + //Add device/system info in the future + //TODO attach previous hash id + + sendRPCMessagePrivate(rai, true); + } else { + Log.e(TAG, "App config was null, soo..."); + } + } + } + } + + void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig) { + } + + void onProtocolSessionStartedNACKed(SessionType sessionType) { + } + + void onProtocolSessionEnded(SessionType sessionType) { + } + + void onProtocolSessionEndedNACKed(SessionType sessionType) { + } + + void startVideoService(boolean encrypted, VideoStreamingParameters parameters) { + } + + boolean endVideoStream() { + return false; + } + + void startAudioService(boolean encrypted) { + } + + boolean endAudioStream() { + return false; + } + + void setSdlSecurityStaticVars() { + } + + /* ******************************************************************************************************* + ********************************** Platform specific methods - End ************************************* + *********************************************************************************************************/ } diff --git a/base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java b/base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java index f8d59261a..714686a96 100644 --- a/base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java +++ b/base/src/main/java/com/smartdevicelink/managers/lifecycle/PoliciesFetcher.java @@ -55,7 +55,7 @@ import java.util.Vector; class PoliciesFetcher { private static final String TAG = PoliciesFetcher.class.getSimpleName(); - private static final int POLICIES_CORRELATION_ID = 65535; + static final int POLICIES_CORRELATION_ID = 65535; private static HttpURLConnection getURLConnection(Headers myHeader, String sURLString, int Timeout, int iContentLen) { String sContentType = "application/json"; diff --git a/base/src/main/java/com/smartdevicelink/util/NativeLogTool.java b/base/src/main/java/com/smartdevicelink/util/NativeLogTool.java index f125fb5ac..ced98a083 100644 --- a/base/src/main/java/com/smartdevicelink/util/NativeLogTool.java +++ b/base/src/main/java/com/smartdevicelink/util/NativeLogTool.java @@ -130,7 +130,7 @@ public class NativeLogTool { break;
}
if (bytesWritten < chunk.length()) {
- Log.e(TAG, "Calling Log.e: msg length=" + chunk.length() + ", bytesWritten=" + bytesWritten);
+ Log.w(TAG, "Calling Log.e: msg length=" + chunk.length() + ", bytesWritten=" + bytesWritten);
}
}
} catch (Exception ex) {
diff --git a/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle b/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle new file mode 120000 index 000000000..d72cada51 --- /dev/null +++ b/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle @@ -0,0 +1 @@ +../../../../../../../base/src/main/java/com/smartdevicelink/managers/lifecycle/
\ No newline at end of file diff --git a/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleConfigurationUpdate.java b/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleConfigurationUpdate.java deleted file mode 120000 index 62dacca1e..000000000 --- a/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleConfigurationUpdate.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../base/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleConfigurationUpdate.java
\ No newline at end of file diff --git a/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java b/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java deleted file mode 120000 index 88f7feccc..000000000 --- a/baseAndroid/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java +++ /dev/null @@ -1 +0,0 @@ -../../../../../../../../base/src/main/java/com/smartdevicelink/managers/lifecycle/RpcConverter.java
\ No newline at end of file diff --git a/hello_sdl_java/src/main/java/com/smartdevicelink/java/Main.java b/hello_sdl_java/src/main/java/com/smartdevicelink/java/Main.java index 446c267ee..f34ab0df2 100644 --- a/hello_sdl_java/src/main/java/com/smartdevicelink/java/Main.java +++ b/hello_sdl_java/src/main/java/com/smartdevicelink/java/Main.java @@ -68,8 +68,10 @@ public class Main { static SdlService.SdlServiceCallback serviceCallback = new SdlService.SdlServiceCallback() { @Override public void onEnd() { - thread.interrupt(); - thread = null; + if (thread != null) { + thread.interrupt(); + thread = null; + } synchronized (LOCK) { LOCK.notify(); } diff --git a/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java b/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java index 6e473c46d..a711438cc 100644 --- a/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java +++ b/javaSE/src/main/java/com/smartdevicelink/managers/SdlManager.java @@ -36,808 +36,156 @@ import android.support.annotation.NonNull; import android.util.Log; import com.smartdevicelink.managers.file.FileManager; -import com.smartdevicelink.managers.file.FileManagerConfig; -import com.smartdevicelink.managers.file.filetypes.SdlArtwork; -import com.smartdevicelink.managers.lifecycle.LifecycleConfigurationUpdate; -import com.smartdevicelink.managers.lifecycle.LifecycleManager; import com.smartdevicelink.managers.permission.PermissionManager; import com.smartdevicelink.managers.screen.ScreenManager; -import com.smartdevicelink.protocol.enums.FunctionID; -import com.smartdevicelink.protocol.enums.SessionType; -import com.smartdevicelink.proxy.RPCMessage; -import com.smartdevicelink.proxy.RPCRequest; -import com.smartdevicelink.proxy.RPCResponse; -import com.smartdevicelink.proxy.SystemCapabilityManager; -import com.smartdevicelink.proxy.interfaces.ISdl; -import com.smartdevicelink.proxy.rpc.ChangeRegistration; -import com.smartdevicelink.proxy.rpc.OnHMIStatus; -import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; -import com.smartdevicelink.proxy.rpc.SetAppIcon; -import com.smartdevicelink.proxy.rpc.TTSChunk; -import com.smartdevicelink.proxy.rpc.TemplateColorScheme; -import com.smartdevicelink.proxy.rpc.enums.AppHMIType; -import com.smartdevicelink.proxy.rpc.enums.Language; -import com.smartdevicelink.proxy.rpc.enums.Result; import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason; -import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCRequestListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener; -import com.smartdevicelink.security.SdlSecurityBase; -import com.smartdevicelink.transport.BaseTransportConfig; import com.smartdevicelink.transport.enums.TransportType; import com.smartdevicelink.util.DebugTool; -import com.smartdevicelink.util.Version; - -import org.json.JSONException; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Vector; - /** * <strong>SDLManager</strong> <br> - * + * <p> * This is the main point of contact between an application and SDL <br> - * + * <p> * It is broken down to these areas: <br> - * + * <p> * 1. SDLManagerBuilder <br> * 2. ISdl Interface along with its overridden methods - This can be passed into attached managers <br> * 3. Sending Requests <br> * 4. Helper methods */ -public class SdlManager extends BaseSdlManager{ - - private static final String TAG = "SdlManager"; - - private SdlArtwork appIcon; - private SdlManagerListener managerListener; - private List<Class<? extends SdlSecurityBase>> sdlSecList; - private ServiceEncryptionListener serviceEncryptionListener; - private FileManagerConfig fileManagerConfig; - - // Managers - private LifecycleManager lifecycleManager; - private PermissionManager permissionManager; - private FileManager fileManager; - private ScreenManager screenManager; - - - // INTERNAL INTERFACE - /** - * This is from the LifeCycleManager directly. In the future if there is a reason to be a man in the middle - * the SdlManager could create it's own, however right now it was only a duplication of logic tied to the LCM. - */ - private ISdl _internalInterface; - - - // Initialize proxyBridge with anonymous lifecycleListener - private final LifecycleManager.LifecycleListener lifecycleListener = new LifecycleManager.LifecycleListener() { - boolean initStarted = false; - @Override - public void onProxyConnected(LifecycleManager lifeCycleManager) { - Log.i(TAG,"Proxy is connected. Now initializing."); - synchronized (this){ - if(!initStarted){ - changeRegistrationRetry = 0; - checkLifecycleConfiguration(); - initialize(); - initStarted = true; - } - } - } - @Override - public void onServiceStarted(SessionType sessionType){ - - } - - @Override - public void onServiceEnded(SessionType sessionType){ - - } - - @Override - public void onProxyClosed(LifecycleManager lifeCycleManager, String info, Exception e, SdlDisconnectedReason reason) { - Log.i(TAG,"Proxy is closed."); - if(managerListener != null){ - managerListener.onDestroy(SdlManager.this); - } - - } - - - @Override - public void onError(LifecycleManager lifeCycleManager, String info, Exception e) { - - } - }; - - // Sub manager listener - private final CompletionListener subManagerListener = new CompletionListener() { - @Override - public synchronized void onComplete(boolean success) { - if(!success){ - Log.e(TAG, "Sub manager failed to initialize"); - } - checkState(); - } - }; - - @Override - void checkState() { - if (permissionManager != null && fileManager != null && screenManager != null ){ - if (permissionManager.getState() == BaseSubManager.READY && fileManager.getState() == BaseSubManager.READY && screenManager.getState() == BaseSubManager.READY){ - DebugTool.logInfo("Starting sdl manager, all sub managers are in ready state"); - transitionToState(BaseSubManager.READY); - handleQueuedNotifications(); - notifyDevListener(null); - onReady(); - } else if (permissionManager.getState() == BaseSubManager.ERROR && fileManager.getState() == BaseSubManager.ERROR && screenManager.getState() == BaseSubManager.ERROR){ - String info = "ERROR starting sdl manager, all sub managers are in error state"; - Log.e(TAG, info); - transitionToState(BaseSubManager.ERROR); - notifyDevListener(info); - } else if (permissionManager.getState() == BaseSubManager.SETTING_UP || fileManager.getState() == BaseSubManager.SETTING_UP || screenManager.getState() == BaseSubManager.SETTING_UP) { - DebugTool.logInfo("SETTING UP sdl manager, some sub managers are still setting up"); - transitionToState(BaseSubManager.SETTING_UP); - // No need to notify developer here! - } else { - Log.w(TAG, "LIMITED starting sdl manager, some sub managers are in error or limited state and the others finished setting up"); - transitionToState(BaseSubManager.LIMITED); - handleQueuedNotifications(); - notifyDevListener(null); - onReady(); - } - } else { - // We should never be here, but somehow one of the sub-sub managers is null - String info = "ERROR one of the sdl sub managers is null"; - Log.e(TAG, info); - transitionToState(BaseSubManager.ERROR); - notifyDevListener(info); - } - } - - private void notifyDevListener(String info) { - if (managerListener != null) { - if (getState() == BaseSubManager.ERROR){ - managerListener.onError(this, info, null); - } else { - managerListener.onStart(this); - } - } - } - - private void onReady(){ - // Set the app icon - if (SdlManager.this.appIcon != null && SdlManager.this.appIcon.getName() != null) { - if (fileManager != null && fileManager.getState() == BaseSubManager.READY && !fileManager.hasUploadedFile(SdlManager.this.appIcon)) { - fileManager.uploadArtwork(SdlManager.this.appIcon, new CompletionListener() { - @Override - public void onComplete(boolean success) { - if (success) { - SetAppIcon msg = new SetAppIcon(SdlManager.this.appIcon.getName()); - _internalInterface.sendRPCRequest(msg); - } - } - }); - } else { - SetAppIcon msg = new SetAppIcon(SdlManager.this.appIcon.getName()); - _internalInterface.sendRPCRequest(msg); - } - } - } - - @Override - protected void checkLifecycleConfiguration(){ - final Language actualLanguage = this.getRegisterAppInterfaceResponse().getLanguage(); - final Language actualHMILanguage = this.getRegisterAppInterfaceResponse().getHmiDisplayLanguage(); - - if ((actualLanguage != null && !actualLanguage.equals(language)) || (actualHMILanguage != null && !actualHMILanguage.equals(hmiLanguage))) { - - LifecycleConfigurationUpdate lcuNew = managerListener.managerShouldUpdateLifecycle(actualLanguage, actualHMILanguage); - LifecycleConfigurationUpdate lcuOld = managerListener.managerShouldUpdateLifecycle(actualLanguage); - final LifecycleConfigurationUpdate lcu; - ChangeRegistration changeRegistration; - if (lcuNew == null) { - lcu = lcuOld; - changeRegistration = new ChangeRegistration(actualLanguage, actualLanguage); - } else { - lcu = lcuNew; - changeRegistration = new ChangeRegistration(actualLanguage, actualHMILanguage); - } - - if (lcu != null) { - changeRegistration.setAppName(lcu.getAppName()); - changeRegistration.setNgnMediaScreenAppName(lcu.getShortAppName()); - changeRegistration.setTtsName(lcu.getTtsName()); - changeRegistration.setVrSynonyms(lcu.getVoiceRecognitionCommandNames()); - changeRegistration.setOnRPCResponseListener(new OnRPCResponseListener() { - @Override - public void onResponse(int correlationId, RPCResponse response) { - if (response.getSuccess()){ - // go through and change sdlManager properties that were changed via the LCU update - hmiLanguage = actualHMILanguage; - language = actualLanguage; - - if (lcu.getAppName() != null) { - appName = lcu.getAppName(); - } - - if (lcu.getShortAppName() != null) { - shortAppName = lcu.getShortAppName(); - } - - if (lcu.getTtsName() != null) { - ttsChunks = lcu.getTtsName(); - } - - if (lcu.getVoiceRecognitionCommandNames() != null) { - vrSynonyms = lcu.getVoiceRecognitionCommandNames(); - } - } - try { - Log.v(TAG, response.serializeJSON().toString()); - } catch (JSONException e) { - e.printStackTrace(); - } - } - - @Override - public void onError(int correlationId, Result resultCode, String info) { - Log.e(TAG, "Change Registration onError: " + resultCode + " | Info: " + info); - } - }); - _internalInterface.sendRPC(changeRegistration); - } - } - } - - @Override - protected void initialize(){ - // Instantiate sub managers - this.permissionManager = new PermissionManager(_internalInterface); - this.fileManager = new FileManager(_internalInterface, fileManagerConfig); - this.screenManager = new ScreenManager(_internalInterface, this.fileManager); - - // Start sub managers - this.permissionManager.start(subManagerListener); - this.fileManager.start(subManagerListener); - this.screenManager.start(subManagerListener); - } - - @Override - public void dispose() { - if (this.permissionManager != null) { - this.permissionManager.dispose(); - } - - if (this.fileManager != null) { - this.fileManager.dispose(); - } - - if (this.screenManager != null) { - this.screenManager.dispose(); - } - - if (this.lifecycleManager != null) { - this.lifecycleManager.stop(); - } - - if(managerListener != null){ - managerListener.onDestroy(this); - managerListener = null; - } - - transitionToState(BaseSubManager.SHUTDOWN); - } - - - // MANAGER GETTERS - /** - * Gets the PermissionManager. <br> - * <strong>Note: PermissionManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a PermissionManager object - */ - public PermissionManager getPermissionManager() { - if (permissionManager.getState() != BaseSubManager.READY && permissionManager.getState() != BaseSubManager.LIMITED){ - Log.e(TAG,"PermissionManager should not be accessed because it is not in READY/LIMITED state"); - } - checkSdlManagerState(); - return permissionManager; - } - - /** - * Gets the FileManager. <br> - * <strong>Note: FileManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a FileManager object - */ - public FileManager getFileManager() { - if (fileManager.getState() != BaseSubManager.READY && fileManager.getState() != BaseSubManager.LIMITED){ - Log.e(TAG, "FileManager should not be accessed because it is not in READY/LIMITED state"); - } - checkSdlManagerState(); - return fileManager; - } - - /** - * Gets the ScreenManager. <br> - * <strong>Note: ScreenManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a ScreenManager object - */ - public ScreenManager getScreenManager() { - if (screenManager.getState() != BaseSubManager.READY && screenManager.getState() != BaseSubManager.LIMITED){ - Log.e(TAG, "ScreenManager should not be accessed because it is not in READY/LIMITED state"); - } - checkSdlManagerState(); - return screenManager; - } - - /** - * Gets the SystemCapabilityManager. <br> - * <strong>Note: SystemCapabilityManager should be used only after SdlManager.start() CompletionListener callback is completed successfully.</strong> - * @return a SystemCapabilityManager object - */ - public SystemCapabilityManager getSystemCapabilityManager(){ - return lifecycleManager.getSystemCapabilityManager(this); - } - - /** - * Method to retrieve the RegisterAppInterface Response message that was sent back from the - * module. It contains various attributes about the connected module and can be used to adapt - * to different module types and their supported features. - * - * @return RegisterAppInterfaceResponse received from the module or null if the app has not yet - * registered with the module. - */ - @Override - public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse(){ - if(lifecycleManager != null){ - return lifecycleManager.getRegisterAppInterfaceResponse(); - } - return null; - } - - /** - * Get the current OnHMIStatus - * @return OnHMIStatus object represents the current OnHMIStatus - */ - @Override - public OnHMIStatus getCurrentHMIStatus(){ - if(this.lifecycleManager !=null ){ - return lifecycleManager.getCurrentHMIStatus(); - } - return null; - } - - // PROTECTED GETTERS - - protected FileManagerConfig getFileManagerConfig() { return fileManagerConfig; } - - /** - * Retrieves the auth token, if any, that was attached to the StartServiceACK for the RPC - * service from the module. For example, this should be used to login to a user account. - * @return the string representation of the auth token - */ - @Override - public String getAuthToken(){ - return this.lifecycleManager.getAuthToken(); - } - - // SENDING REQUESTS - - /** - * Send RPC Message <br> - * @param message RPCMessage - */ - @Override - public void sendRPC(RPCMessage message) { - _internalInterface.sendRPC(message); - } - - /** - * Takes a list of RPCMessages and sends it to SDL in a synchronous fashion. Responses are captured through callback on OnMultipleRequestListener. - * For sending requests asynchronously, use sendRequests <br> - * - * <strong>NOTE: This will override any listeners on individual RPCs</strong><br> - * - * <strong>ADDITIONAL NOTE: This only takes the type of RPCRequest for now, notifications and responses will be thrown out</strong> - * - * @param rpcs is the list of RPCMessages being sent - * @param listener listener for updates and completions - */ - @Override - public void sendSequentialRPCs(final List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener){ - - List<RPCRequest> rpcRequestList = new ArrayList<>(); - for (int i = 0; i < rpcs.size(); i++) { - if (rpcs.get(i) instanceof RPCRequest){ - rpcRequestList.add((RPCRequest)rpcs.get(i)); - } - } - - if (rpcRequestList.size() > 0) { - _internalInterface.sendSequentialRPCs(rpcRequestList, listener); - } - } - - /** - * Takes a list of RPCMessages and sends it to SDL. Responses are captured through callback on OnMultipleRequestListener. - * For sending requests synchronously, use sendSequentialRPCs <br> - * - * <strong>NOTE: This will override any listeners on individual RPCs</strong> <br> - * - * <strong>ADDITIONAL NOTE: This only takes the type of RPCRequest for now, notifications and responses will be thrown out</strong> - * - * @param rpcs is the list of RPCMessages being sent - * @param listener listener for updates and completions - */ - @Override - public void sendRPCs(List<? extends RPCMessage> rpcs, final OnMultipleRequestListener listener) { - - List<RPCRequest> rpcRequestList = new ArrayList<>(); - for (int i = 0; i < rpcs.size(); i++) { - if (rpcs.get(i) instanceof RPCRequest){ - rpcRequestList.add((RPCRequest)rpcs.get(i)); - } - } - - if (rpcRequestList.size() > 0) { - _internalInterface.sendRequests(rpcRequestList,listener); - } - } - - /** - * Add an OnRPCNotificationListener - * @param listener listener that will be called when a notification is received - */ - @Override - public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener){ - _internalInterface.addOnRPCNotificationListener(notificationId,listener); - } - - /** - * Remove an OnRPCNotificationListener - * @param listener listener that was previously added - */ - @Override - public void removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener){ - _internalInterface.removeOnRPCNotificationListener(notificationId, listener); - } - - /** - * Add an OnRPCRequestListener - * @param listener listener that will be called when a request is received - */ - @Override - public void addOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener){ - _internalInterface.addOnRPCRequestListener(requestId,listener); - } - - /** - * Remove an OnRPCRequestListener - * @param listener listener that was previously added - */ - @Override - public void removeOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener){ - _internalInterface.removeOnRPCRequestListener(requestId, listener); - } - - // LIFECYCLE / OTHER - - // STARTUP - - /** - * Starts up a SdlManager, and calls provided callback called once all BaseSubManagers are done setting up - */ - @SuppressWarnings("unchecked") - @Override - public void start(){ - - Runtime.getRuntime().addShutdownHook(new Thread() { - @Override - public void run() { - dispose(); - } - }); - - Log.i(TAG, "start"); - if (lifecycleManager == null) { - if (transport != null - && (transport.getTransportType().equals(TransportType.WEB_SOCKET_SERVER) || transport.getTransportType().equals(TransportType.CUSTOM))) { - //Do the thing - - LifecycleManager.AppConfig appConfig = new LifecycleManager.AppConfig(); - appConfig.setAppName(appName); - //short app name - appConfig.setMediaApp(isMediaApp); - appConfig.setHmiDisplayLanguageDesired(hmiLanguage); - appConfig.setLanguageDesired(hmiLanguage); - appConfig.setAppType(hmiTypes); - appConfig.setVrSynonyms(vrSynonyms); - appConfig.setTtsName(ttsChunks); - appConfig.setDayColorScheme(dayColorScheme); - appConfig.setNightColorScheme(nightColorScheme); - appConfig.setAppID(appId); - appConfig.setMinimumProtocolVersion(minimumProtocolVersion); - appConfig.setMinimumRPCVersion(minimumRPCVersion); - - lifecycleManager = new LifecycleManager(appConfig, transport, lifecycleListener); - _internalInterface = lifecycleManager.getInternalInterface(SdlManager.this); - - if (sdlSecList != null && !sdlSecList.isEmpty()) { - lifecycleManager.setSdlSecurity(sdlSecList, serviceEncryptionListener); - } - - //Setup the notification queue - initNotificationQueue(); - - lifecycleManager.start(); - - - }else{ - throw new RuntimeException("No transport provided"); - } - } - } - - - // BUILDER - public static class Builder { - SdlManager sdlManager; - - /** - * Builder for the SdlManager. Parameters in the constructor are required. - * @param appId the app's ID - * @param appName the app's name - * @param listener a SdlManagerListener object - */ - public Builder(@NonNull final String appId, @NonNull final String appName, @NonNull final SdlManagerListener listener){ - sdlManager = new SdlManager(); - setAppId(appId); - setAppName(appName); - setManagerListener(listener); - } - - /** - * Sets the App ID - * @param appId String representation of the App ID retreived from the SDL Developer Portal - */ - public Builder setAppId(@NonNull final String appId){ - sdlManager.appId = appId; - return this; - } - - /** - * Sets the Application Name - * @param appName String that will be associated as the app's name - */ - public Builder setAppName(@NonNull final String appName){ - sdlManager.appName = appName; - return this; - } - - /** - * Sets the Short Application Name - * @param shortAppName a shorter representation of the app's name for smaller displays - */ - public Builder setShortAppName(final String shortAppName) { - sdlManager.shortAppName = shortAppName; - return this; - } - - /** - * Sets the minimum protocol version that will be permitted to connect. - * If the protocol version of the head unit connected is below this version, - * the app will disconnect with an EndService protocol message and will not register. - * @param minimumProtocolVersion the minimum Protocol spec version that should be accepted - */ - public Builder setMinimumProtocolVersion(final Version minimumProtocolVersion) { - sdlManager.minimumProtocolVersion = minimumProtocolVersion; - return this; - } - - /** - * The minimum RPC version that will be permitted to connect. - * If the RPC version of the head unit connected is below this version, an UnregisterAppInterface will be sent. - * @param minimumRPCVersion the minimum RPC spec version that should be accepted - */ - public Builder setMinimumRPCVersion(final Version minimumRPCVersion) { - sdlManager.minimumRPCVersion = minimumRPCVersion; - return this; - } - - /** - * Sets the Language of the App - * @param hmiLanguage the desired language to be used on the display/HMI of the connected module - */ - public Builder setLanguage(final Language hmiLanguage) { - sdlManager.hmiLanguage = hmiLanguage; - sdlManager.language = hmiLanguage; - return this; - } - - /** - * Sets the TemplateColorScheme for daytime - * @param dayColorScheme color scheme that will be used (if supported) when the display is in a "Day Mode" or - * similar. Should comprise of colors that contrast well during the day under sunlight. - */ - public Builder setDayColorScheme(final TemplateColorScheme dayColorScheme){ - sdlManager.dayColorScheme = dayColorScheme; - return this; - } - - /** - * Sets the TemplateColorScheme for nighttime - * @param nightColorScheme color scheme that will be used (if supported) when the display is in a "Night Mode" - * or similar. Should comprise of colors that contrast well during the night and are not - * brighter than average. - */ - public Builder setNightColorScheme(final TemplateColorScheme nightColorScheme){ - sdlManager.nightColorScheme = nightColorScheme; - return this; - } - - /** - * Sets the icon for the app on head unit / In-Vehicle-Infotainment system <br> - * @param sdlArtwork the icon that will be used to represent this application on the connected module - */ - public Builder setAppIcon(final SdlArtwork sdlArtwork){ - sdlManager.appIcon = sdlArtwork; - return this; - } - - /** - * Sets the vector of AppHMIType <br> - * <strong>Note: This should be an ordered list from most -> least relevant</strong> - * @param hmiTypes HMI types that represent this application. For example, if the app is a music player, the - * MEDIA HMIType should be included. - */ - public Builder setAppTypes(final Vector<AppHMIType> hmiTypes){ - - sdlManager.hmiTypes = hmiTypes; - - if (hmiTypes != null) { - sdlManager.isMediaApp = hmiTypes.contains(AppHMIType.MEDIA); - } - - return this; - } - - /** - * Sets the FileManagerConfig for the session.<br> - * <strong>Note: If not set, the default configuration value of 1 will be set for - * artworkRetryCount and fileRetryCount in FileManagerConfig</strong> - * @param fileManagerConfig - configuration options - */ - public Builder setFileManagerConfig (final FileManagerConfig fileManagerConfig){ - sdlManager.fileManagerConfig = fileManagerConfig; - return this; - } - - /** - * Sets the voice recognition synonyms that can be used to identify this application. - * @param vrSynonyms a vector of Strings that can be associated with this app. For example the app's name should - * be included as well as any phonetic spellings of the app name that might help the on-board - * VR system associated a users spoken word with the supplied synonyms. - */ - public Builder setVrSynonyms(final Vector<String> vrSynonyms) { - sdlManager.vrSynonyms = vrSynonyms; - return this; - } - - /** - * Sets the Text-To-Speech Name of the application. These TTSChunks might be used by the module as an audio - * representation of the app's name. - * @param ttsChunks the TTS chunks that can represent this app's name - */ - public Builder setTtsName(final Vector<TTSChunk> ttsChunks) { - sdlManager.ttsChunks = ttsChunks; - return this; - } - - /** - * This Object type may change with the transport refactor - * Sets the BaseTransportConfig - * @param transport the type of transport that should be used for this SdlManager instance. - */ - public Builder setTransportType(BaseTransportConfig transport){ - sdlManager.transport = transport; - return this; - } - - /** - * Sets the Security libraries - * @param secList The list of security class(es) - */ - @Deprecated - public Builder setSdlSecurity(List<Class<? extends SdlSecurityBase>> secList) { - sdlManager.sdlSecList = secList; - return this; - } - - /** - * Sets the security libraries and a callback to notify caller when there is update to encryption service - * @param secList The list of security class(es) - * @param listener The callback object - */ - public Builder setSdlSecurity(@NonNull List<Class<? extends SdlSecurityBase>> secList, ServiceEncryptionListener listener) { - sdlManager.sdlSecList = secList; - sdlManager.serviceEncryptionListener = listener; - return this; - } - - /** - * Set the SdlManager Listener - * @param listener the listener - */ - public Builder setManagerListener(@NonNull final SdlManagerListener listener){ - sdlManager.managerListener = listener; - return this; - } - - /** - * Set RPCNotification listeners. SdlManager will preload these listeners before any RPCs are sent/received. - * @param listeners a map of listeners that will be called when a notification is received. - * Key represents the FunctionID of the notification and value represents the listener - */ - public Builder setRPCNotificationListeners(Map<FunctionID, OnRPCNotificationListener> listeners){ - sdlManager.onRPCNotificationListeners = listeners; - return this; - } - - public SdlManager build() { - - if (sdlManager.appName == null) { - throw new IllegalArgumentException("You must specify an app name by calling setAppName"); - } - - if (sdlManager.appId == null) { - throw new IllegalArgumentException("You must specify an app ID by calling setAppId"); - } - - if (sdlManager.managerListener == null) { - throw new IllegalArgumentException("You must set a SdlManagerListener object"); - } - - if (sdlManager.hmiTypes == null) { - Vector<AppHMIType> hmiTypesDefault = new Vector<>(); - hmiTypesDefault.add(AppHMIType.DEFAULT); - sdlManager.hmiTypes = hmiTypesDefault; - sdlManager.isMediaApp = false; - } - if(sdlManager.fileManagerConfig == null){ - //if FileManagerConfig is not set use default - sdlManager.fileManagerConfig = new FileManagerConfig(); - } - - if (sdlManager.hmiLanguage == null) { - sdlManager.hmiLanguage = Language.EN_US; - sdlManager.language = Language.EN_US; - } - - if (sdlManager.minimumProtocolVersion == null){ - sdlManager.minimumProtocolVersion = new Version("1.0.0"); - } - - if (sdlManager.minimumRPCVersion == null){ - sdlManager.minimumRPCVersion = new Version("1.0.0"); - } - - sdlManager.transitionToState(BaseSubManager.SETTING_UP); - - return sdlManager; - } - } - - /** - * Start a secured RPC service - */ - public void startRPCEncryption() { - if (lifecycleManager != null) { - lifecycleManager.startRPCEncryption(); - } - } +public class SdlManager extends BaseSdlManager { + + /** + * Starts up a SdlManager, and calls provided callback called once all BaseSubManagers are done setting up + */ + @Override + public void start() { + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + dispose(); + } + }); + + Log.i(TAG, "start"); + if (lifecycleManager == null) { + if (transport != null && (transport.getTransportType().equals(TransportType.WEB_SOCKET_SERVER) || transport.getTransportType().equals(TransportType.CUSTOM))) { + super.start(); + lifecycleManager.start(); + } else { + throw new RuntimeException("No transport provided"); + } + } + } + + @Override + protected void initialize() { + // Instantiate sub managers + this.permissionManager = new PermissionManager(_internalInterface); + this.fileManager = new FileManager(_internalInterface, fileManagerConfig); + this.screenManager = new ScreenManager(_internalInterface, this.fileManager); + + // Start sub managers + this.permissionManager.start(subManagerListener); + this.fileManager.start(subManagerListener); + this.screenManager.start(subManagerListener); + } + + @Override + void checkState() { + if (permissionManager != null && fileManager != null && screenManager != null) { + if (permissionManager.getState() == BaseSubManager.READY && fileManager.getState() == BaseSubManager.READY && screenManager.getState() == BaseSubManager.READY) { + DebugTool.logInfo("Starting sdl manager, all sub managers are in ready state"); + transitionToState(BaseSubManager.READY); + handleQueuedNotifications(); + notifyDevListener(null); + onReady(); + } else if (permissionManager.getState() == BaseSubManager.ERROR && fileManager.getState() == BaseSubManager.ERROR && screenManager.getState() == BaseSubManager.ERROR) { + String info = "ERROR starting sdl manager, all sub managers are in error state"; + Log.e(TAG, info); + transitionToState(BaseSubManager.ERROR); + notifyDevListener(info); + } else if (permissionManager.getState() == BaseSubManager.SETTING_UP || fileManager.getState() == BaseSubManager.SETTING_UP || screenManager.getState() == BaseSubManager.SETTING_UP) { + DebugTool.logInfo("SETTING UP sdl manager, some sub managers are still setting up"); + transitionToState(BaseSubManager.SETTING_UP); + // No need to notify developer here! + } else { + Log.w(TAG, "LIMITED starting sdl manager, some sub managers are in error or limited state and the others finished setting up"); + transitionToState(BaseSubManager.LIMITED); + handleQueuedNotifications(); + notifyDevListener(null); + onReady(); + } + } else { + // We should never be here, but somehow one of the sub-sub managers is null + String info = "ERROR one of the sdl sub managers is null"; + Log.e(TAG, info); + transitionToState(BaseSubManager.ERROR); + notifyDevListener(info); + } + } + + private void notifyDevListener(String info) { + if (managerListener != null) { + if (getState() == BaseSubManager.ERROR) { + managerListener.onError((SdlManager) this, info, null); + } else { + managerListener.onStart((SdlManager) this); + } + } + } + + @Override + void retryChangeRegistration() { + // Do nothing + } + + @Override + void onProxyClosed(SdlDisconnectedReason reason) { + Log.i(TAG, "Proxy is closed."); + if (managerListener != null) { + managerListener.onDestroy(SdlManager.this); + } + } + + @Override + public void dispose() { + if (this.permissionManager != null) { + this.permissionManager.dispose(); + } + + if (this.fileManager != null) { + this.fileManager.dispose(); + } + + if (this.screenManager != null) { + this.screenManager.dispose(); + } + + if (this.lifecycleManager != null) { + this.lifecycleManager.stop(); + } + + if (managerListener != null) { + managerListener.onDestroy((SdlManager) this); + managerListener = null; + } + + transitionToState(BaseSubManager.SHUTDOWN); + } + + // BUILDER + public static class Builder extends BaseSdlManager.Builder { + /** + * Builder for the SdlManager. Parameters in the constructor are required. + * + * @param appId the app's ID + * @param appName the app's name + * @param listener a SdlManagerListener object + */ + public Builder(@NonNull final String appId, @NonNull final String appName, @NonNull final SdlManagerListener listener) { + super(appId, appName, listener); + } + } } diff --git a/javaSE/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java b/javaSE/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java index 0beb1da0e..4a5e3747d 100644 --- a/javaSE/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java +++ b/javaSE/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java @@ -32,74 +32,10 @@ package com.smartdevicelink.managers.lifecycle; -import android.support.annotation.NonNull; import android.support.annotation.RestrictTo; -import android.util.Log; -import com.smartdevicelink.SdlConnection.ISdlConnectionListener; import com.smartdevicelink.SdlConnection.SdlSession; -import com.smartdevicelink.exception.SdlException; -import com.smartdevicelink.managers.SdlManager; -import com.smartdevicelink.managers.ServiceEncryptionListener; -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.RPCMessage; -import com.smartdevicelink.proxy.RPCNotification; -import com.smartdevicelink.proxy.RPCRequest; -import com.smartdevicelink.proxy.RPCResponse; -import com.smartdevicelink.proxy.SystemCapabilityManager; -import com.smartdevicelink.proxy.interfaces.IAudioStreamListener; -import com.smartdevicelink.proxy.interfaces.ISdl; -import com.smartdevicelink.proxy.interfaces.ISdlServiceListener; -import com.smartdevicelink.proxy.interfaces.IVideoStreamListener; -import com.smartdevicelink.proxy.interfaces.OnSystemCapabilityListener; -import com.smartdevicelink.proxy.rpc.OnAppInterfaceUnregistered; -import com.smartdevicelink.proxy.rpc.OnButtonEvent; -import com.smartdevicelink.proxy.rpc.OnButtonPress; -import com.smartdevicelink.proxy.rpc.OnHMIStatus; -import com.smartdevicelink.proxy.rpc.OnSystemRequest; -import com.smartdevicelink.proxy.rpc.RegisterAppInterface; -import com.smartdevicelink.proxy.rpc.RegisterAppInterfaceResponse; -import com.smartdevicelink.proxy.rpc.SdlMsgVersion; -import com.smartdevicelink.proxy.rpc.SubscribeButton; -import com.smartdevicelink.proxy.rpc.SystemRequest; -import com.smartdevicelink.proxy.rpc.TTSChunk; -import com.smartdevicelink.proxy.rpc.TemplateColorScheme; -import com.smartdevicelink.proxy.rpc.UnregisterAppInterface; -import com.smartdevicelink.proxy.rpc.VehicleType; -import com.smartdevicelink.proxy.rpc.enums.AppHMIType; -import com.smartdevicelink.proxy.rpc.enums.AppInterfaceUnregisteredReason; -import com.smartdevicelink.proxy.rpc.enums.ButtonName; -import com.smartdevicelink.proxy.rpc.enums.FileType; -import com.smartdevicelink.proxy.rpc.enums.HMILevel; -import com.smartdevicelink.proxy.rpc.enums.Language; -import com.smartdevicelink.proxy.rpc.enums.RequestType; -import com.smartdevicelink.proxy.rpc.enums.Result; -import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason; -import com.smartdevicelink.proxy.rpc.enums.SystemCapabilityType; -import com.smartdevicelink.proxy.rpc.listeners.OnMultipleRequestListener; -import com.smartdevicelink.proxy.rpc.listeners.OnPutFileUpdateListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCNotificationListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCRequestListener; -import com.smartdevicelink.proxy.rpc.listeners.OnRPCResponseListener; -import com.smartdevicelink.security.SdlSecurityBase; -import com.smartdevicelink.streaming.audio.AudioStreamingCodec; -import com.smartdevicelink.streaming.audio.AudioStreamingParams; -import com.smartdevicelink.streaming.video.VideoStreamingParameters; import com.smartdevicelink.transport.BaseTransportConfig; -import com.smartdevicelink.util.CorrelationIdGenerator; -import com.smartdevicelink.util.DebugTool; -import com.smartdevicelink.util.FileUtls; -import com.smartdevicelink.util.Version; - -import java.util.HashMap; -import java.util.List; -import java.util.Vector; -import java.util.concurrent.CopyOnWriteArrayList; /** * The lifecycle manager creates a central point for all SDL session logic to converge. It should only be used by @@ -107,1398 +43,21 @@ import java.util.concurrent.CopyOnWriteArrayList; */ @RestrictTo(RestrictTo.Scope.LIBRARY) public class LifecycleManager extends BaseLifecycleManager { - - private static final String TAG = "Lifecycle Manager"; - - public static final Version MAX_SUPPORTED_RPC_VERSION = new Version(6, 0, 0); - - // Protected Correlation IDs - private final int REGISTER_APP_INTERFACE_CORRELATION_ID = 65529, - UNREGISTER_APP_INTERFACE_CORRELATION_ID = 65530; - - - // Sdl Synchronization Objects - private static final Object RPC_LISTENER_LOCK = new Object(), - ON_UPDATE_LISTENER_LOCK = new Object(), - ON_REQUEST_LISTENER_LOCK = new Object(), - ON_NOTIFICATION_LISTENER_LOCK = new Object(); - - - - SdlSession session; - AppConfig appConfig; - - //protected Version protocolVersion = new Version(1,0,0); - protected Version rpcSpecVersion = MAX_SUPPORTED_RPC_VERSION; - - - private final HashMap<Integer,CopyOnWriteArrayList<OnRPCListener>> rpcListeners; - private final HashMap<Integer, OnRPCResponseListener> rpcResponseListeners; - private final HashMap<Integer, CopyOnWriteArrayList<OnRPCNotificationListener>> rpcNotificationListeners; - private final HashMap<Integer, CopyOnWriteArrayList<OnRPCRequestListener>> rpcRequestListeners; - - protected final SystemCapabilityManager systemCapabilityManager; - private EncryptionLifecycleManager encryptionLifecycleManager; - - protected RegisterAppInterfaceResponse raiResponse = null; - - private OnHMIStatus currentHMIStatus; - protected boolean firstTimeFull = true; - - final LifecycleListener lifecycleListener; - - private List<Class<? extends SdlSecurityBase>> _secList = null; - private String authToken; - private Version minimumProtocolVersion; - private Version minimumRPCVersion; - - public LifecycleManager(AppConfig appConfig, BaseTransportConfig config, LifecycleListener listener){ - - this.lifecycleListener = listener; - - this.rpcListeners = new HashMap<>(); - this.rpcResponseListeners = new HashMap<>(); - this.rpcNotificationListeners = new HashMap<>(); - this.rpcRequestListeners = new HashMap<>(); - - this.appConfig = appConfig; - this.minimumProtocolVersion = appConfig.minimumProtocolVersion; - this.minimumRPCVersion = appConfig.minimumRPCVersion; - this.session = new SdlSession(sdlConnectionListener, config); - - this.systemCapabilityManager = new SystemCapabilityManager(internalInterface); - } - - public void start(){ - try { - setupInternalRpcListeners(); - session.startSession(); - } catch (SdlException e) { - e.printStackTrace(); - } - - } - - /** - * Start a secured RPC service - */ - public void startRPCEncryption() { - if (session != null) { - session.startService(SessionType.RPC, session.getSessionId(), true); - } - } - - public void stop(){ - session.close(); - } - - private Version getProtocolVersion(){ - if (session != null){ - return session.getProtocolVersion(); - } - return new Version(1,0,0); - } - - private void sendRPCs(List<? extends RPCMessage> messages, final OnMultipleRequestListener listener){ - if(messages != null ){ - for(RPCMessage message : messages){ - // Request Specifics - if(message instanceof RPCRequest){ - RPCRequest request = ((RPCRequest) message); - final OnRPCResponseListener devOnRPCResponseListener = request.getOnRPCResponseListener(); - request.setCorrelationID(CorrelationIdGenerator.generateId()); - if (listener != null) { - listener.addCorrelationId(request.getCorrelationID()); - request.setOnRPCResponseListener(new OnRPCResponseListener() { - @Override - public void onResponse(int correlationId, RPCResponse response) { - if (devOnRPCResponseListener != null){ - devOnRPCResponseListener.onResponse(correlationId, response); - } - if (listener.getSingleRpcResponseListener() != null) { - listener.getSingleRpcResponseListener().onResponse(correlationId, response); - } - } - - @Override - public void onError(int correlationId, Result resultCode, String info) { - super.onError(correlationId, resultCode, info); - if (devOnRPCResponseListener != null){ - devOnRPCResponseListener.onError(correlationId, resultCode, info); - } - if (listener.getSingleRpcResponseListener() != null) { - listener.getSingleRpcResponseListener().onError(correlationId, resultCode, info); - } - } - }); - } - sendRPCMessagePrivate(request); - }else { - // Notifications and Responses - sendRPCMessagePrivate(message); - if (listener != null){ - listener.onUpdate(messages.size()); - if (messages.size() == 0){ - listener.onFinished(); - } - } - } - } - } - } - - private void sendSequentialRPCs(final List<? extends RPCMessage> messages, final OnMultipleRequestListener listener){ - if (messages != null){ - // Break out of recursion, we have finished the requests - if (messages.size() == 0) { - if(listener != null){ - listener.onFinished(); - } - return; - } - - RPCMessage rpc = messages.remove(0); - - // Request Specifics - if (rpc.getMessageType().equals(RPCMessage.KEY_REQUEST)) { - RPCRequest request = (RPCRequest) rpc; - request.setCorrelationID(CorrelationIdGenerator.generateId()); - - final OnRPCResponseListener devOnRPCResponseListener = request.getOnRPCResponseListener(); - - request.setOnRPCResponseListener(new OnRPCResponseListener() { - @Override - public void onResponse(int correlationId, RPCResponse response) { - if (devOnRPCResponseListener != null){ - devOnRPCResponseListener.onResponse(correlationId, response); - } - if (listener != null) { - listener.onResponse(correlationId, response); - listener.onUpdate(messages.size()); - } - // recurse after onResponse - sendSequentialRPCs(messages, listener); - } - - @Override - public void onError(int correlationId, Result resultCode, String info) { - if (devOnRPCResponseListener != null){ - devOnRPCResponseListener.onError(correlationId, resultCode, info); - } - if (listener != null) { - listener.onError(correlationId, resultCode, info); - listener.onUpdate(messages.size()); - - } - // recurse after onError - sendSequentialRPCs(messages, listener); - } - }); - sendRPCMessagePrivate(request); - } else { - // Notifications and Responses - sendRPCMessagePrivate(rpc); - if (listener != null) { - listener.onUpdate(messages.size()); - } - // recurse after sending a notification or response as there is no response. - sendSequentialRPCs(messages, listener); - } - } - } - - /** - * This method is used to ensure all of the methods in this class can remain private and no grantees can be made - * to the developer what methods are available or not. - * - * <b>NOTE: THERE IS NO GURANTEE THIS WILL BE A VALID SYSTEM CAPABILITY MANAGER</b> - * - * @param sdlManager this must be a working manager instance - * @return the system capability manager. - */ - @RestrictTo(RestrictTo.Scope.LIBRARY) - public SystemCapabilityManager getSystemCapabilityManager(SdlManager sdlManager){ - if(sdlManager != null){ - return systemCapabilityManager; - } - return null; - } - - private boolean isConnected(){ - if(session != null){ - return session.getIsConnected(); - }else{ - return false; - } - } - - /** - * Method to retrieve the RegisterAppInterface Response message that was sent back from the - * module. It contains various attributes about the connected module and can be used to adapt - * to different module types and their supported features. - * - * @return RegisterAppInterfaceResponse received from the module or null if the app has not yet - * registered with the module. - */ - public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse(){ - return this.raiResponse; - } - - - /** - * Get the current OnHMIStatus - * @return OnHMIStatus object represents the current OnHMIStatus - */ - public OnHMIStatus getCurrentHMIStatus() { - return currentHMIStatus; - } - - private void onClose(String info, Exception e){ - Log.i(TAG, "onClose"); - if(lifecycleListener != null){ - lifecycleListener.onProxyClosed(this, info,e,null); - } - } - - /** - * This method is used to ensure all of the methods in this class can remain private and no grantees can be made - * to the developer what methods are available or not. - * - * @param sdlManager this must be a working manager instance - * @return the internal interface that hooks into this manager - */ - @RestrictTo(RestrictTo.Scope.LIBRARY) - public ISdl getInternalInterface(SdlManager sdlManager) { - if (sdlManager != null) { - return internalInterface; - } - return null; - } - - - /* ******************************************************************************************************* - ********************************** INTERNAL - RPC LISTENERS !! START !! ********************************* - *********************************************************************************************************/ - - private void setupInternalRpcListeners(){ - addRpcListener(FunctionID.REGISTER_APP_INTERFACE, rpcListener); - addRpcListener(FunctionID.ON_HMI_STATUS, rpcListener); - addRpcListener(FunctionID.ON_HASH_CHANGE, rpcListener); - addRpcListener(FunctionID.ON_SYSTEM_REQUEST, rpcListener); - addRpcListener(FunctionID.ON_APP_INTERFACE_UNREGISTERED, rpcListener); - addRpcListener(FunctionID.UNREGISTER_APP_INTERFACE, rpcListener); - } - - - private OnRPCListener rpcListener = new OnRPCListener() { - @Override - public void onReceived(RPCMessage message) { - //Make sure this is a response as expected - FunctionID functionID = message.getFunctionID(); - if (functionID != null) { - switch (functionID) { - case REGISTER_APP_INTERFACE: - //We have begun - Log.i(TAG, "RAI Response"); - raiResponse = (RegisterAppInterfaceResponse) message; - SdlMsgVersion rpcVersion = ((RegisterAppInterfaceResponse) message).getSdlMsgVersion(); - if (rpcVersion != null) { - LifecycleManager.this.rpcSpecVersion = new Version(rpcVersion.getMajorVersion(), rpcVersion.getMinorVersion(), rpcVersion.getPatchVersion()); - } else { - LifecycleManager.this.rpcSpecVersion = MAX_SUPPORTED_RPC_VERSION; - } - if (minimumRPCVersion != null && minimumRPCVersion.isNewerThan(rpcSpecVersion) == 1) { - Log.w(TAG, String.format("Disconnecting from head unit, the configured minimum RPC version %s is greater than the supported RPC version %s", minimumRPCVersion, rpcSpecVersion)); - UnregisterAppInterface msg = new UnregisterAppInterface(); - msg.setCorrelationID(UNREGISTER_APP_INTERFACE_CORRELATION_ID); - sendRPCMessagePrivate(msg); - cleanProxy(); - return; - } - processRaiResponse(raiResponse); - systemCapabilityManager.parseRAIResponse(raiResponse); - break; - case ON_HMI_STATUS: - Log.i(TAG, "on hmi status"); - boolean shouldInit = currentHMIStatus == null; - currentHMIStatus = (OnHMIStatus) message; - if (lifecycleListener != null && shouldInit) { - lifecycleListener.onProxyConnected(LifecycleManager.this); - } - break; - case ON_HASH_CHANGE: - break; - case ON_SYSTEM_REQUEST: - final OnSystemRequest onSystemRequest = (OnSystemRequest) message; - if ((onSystemRequest.getUrl() != null) && - (((onSystemRequest.getRequestType() == RequestType.PROPRIETARY) && (onSystemRequest.getFileType() == FileType.JSON)) - || ((onSystemRequest.getRequestType() == RequestType.HTTP) && (onSystemRequest.getFileType() == FileType.BINARY)))) { - Thread handleOffboardTransmissionThread = new Thread() { - @Override - public void run() { - RPCRequest request = PoliciesFetcher.fetchPolicies(onSystemRequest); - if (request != null && isConnected()) { - sendRPCMessagePrivate(request); - } - } - }; - handleOffboardTransmissionThread.start(); - }else if (onSystemRequest.getRequestType() == RequestType.ICON_URL && onSystemRequest.getUrl() != null) { - //Download the icon file and send SystemRequest RPC - Thread handleOffBoardTransmissionThread = new Thread() { - @Override - public void run() { - final String urlHttps = onSystemRequest.getUrl().replaceFirst("http://", "https://"); - byte[] file = FileUtls.downloadFile(urlHttps); - if (file != null) { - SystemRequest systemRequest = new SystemRequest(); - systemRequest.setFileName(onSystemRequest.getUrl()); - systemRequest.setBulkData(file); - systemRequest.setRequestType(RequestType.ICON_URL); - if (isConnected()) { - sendRPCMessagePrivate(systemRequest); - } - } else { - DebugTool.logError("File was null at: " + urlHttps); - } - } - }; - handleOffBoardTransmissionThread.start(); - } - break; - case ON_APP_INTERFACE_UNREGISTERED: - - OnAppInterfaceUnregistered onAppInterfaceUnregistered = (OnAppInterfaceUnregistered) message; - - if (!onAppInterfaceUnregistered.getReason().equals(AppInterfaceUnregisteredReason.LANGUAGE_CHANGE)) { - Log.v(TAG, "on app interface unregistered"); - cleanProxy(); - }else{ - Log.v(TAG, "re-registering for language change"); - processLanguageChange(); - } - break; - case UNREGISTER_APP_INTERFACE: - Log.v(TAG, "unregister app interface"); - cleanProxy(); - break; - } - } - } - - - - }; - - private void processLanguageChange(){ - if (session != null) { - if (session.getIsConnected()) { - session.close(); - } - try { - session.startSession(); - } catch (SdlException e) { - e.printStackTrace(); - } - } - } - - /* ******************************************************************************************************* - ********************************** INTERNAL - RPC LISTENERS !! END !! ********************************* - *********************************************************************************************************/ - - - /* ******************************************************************************************************* - ********************************** METHODS - RPC LISTENERS !! START !! ********************************** - *********************************************************************************************************/ - - private boolean onRPCReceived(final RPCMessage message){ - synchronized(RPC_LISTENER_LOCK){ - if(message == null || message.getFunctionID() == null){ - return false; - } - - final int id = message.getFunctionID().getId(); - CopyOnWriteArrayList<OnRPCListener> listeners = rpcListeners.get(id); - if(listeners!=null && listeners.size()>0) { - for (OnRPCListener listener : listeners) { - listener.onReceived(message); - } - return true; - } - return false; - } - } - - private void addRpcListener(FunctionID id, OnRPCListener listener){ - synchronized(RPC_LISTENER_LOCK){ - if (id != null && listener != null) { - if (!rpcListeners.containsKey(id.getId())) { - rpcListeners.put(id.getId(), new CopyOnWriteArrayList<OnRPCListener>()); - } - - rpcListeners.get(id.getId()).add(listener); - } - } - } - - private boolean removeOnRPCListener(FunctionID id, OnRPCListener listener){ - synchronized(RPC_LISTENER_LOCK){ - if(rpcListeners!= null - && id != null - && listener != null - && rpcListeners.containsKey(id.getId())){ - return rpcListeners.get(id.getId()).remove(listener); - } - } - return false; + public LifecycleManager(AppConfig appConfig, BaseTransportConfig config, LifecycleListener listener) { + super(appConfig, config, listener); } - /** - * Only call this method for a PutFile response. It will cause a class cast exception if not. - * @param correlationId correlation id of the packet being updated - * @param bytesWritten how many bytes were written - * @param totalSize the total size in bytes - */ - @SuppressWarnings("unused") - private void onPacketProgress(int correlationId, long bytesWritten, long totalSize){ - synchronized(ON_UPDATE_LISTENER_LOCK){ - if(rpcResponseListeners !=null - && rpcResponseListeners.containsKey(correlationId)){ - ((OnPutFileUpdateListener)rpcResponseListeners.get(correlationId)).onUpdate(correlationId, bytesWritten, totalSize); - } - } - - } - - /** - * Will provide callback to the listener either onFinish or onError depending on the RPCResponses result code, - * <p>Will automatically remove the listener for the list of listeners on completion. - * @param msg The RPCResponse message that was received - * @return if a listener was called or not - */ - @SuppressWarnings("UnusedReturnValue") - private boolean onRPCResponseReceived(RPCResponse msg){ - synchronized(ON_UPDATE_LISTENER_LOCK){ - int correlationId = msg.getCorrelationID(); - if(rpcResponseListeners !=null - && rpcResponseListeners.containsKey(correlationId)){ - OnRPCResponseListener listener = rpcResponseListeners.get(correlationId); - if(msg.getSuccess()){ - listener.onResponse(correlationId, msg); - }else{ - listener.onError(correlationId, msg.getResultCode(), msg.getInfo()); - } - rpcResponseListeners.remove(correlationId); - return true; - } - return false; - } - } - - /** - * Add a listener that will receive the response to the specific RPCRequest sent with the corresponding correlation id - * @param listener that will get called back when a response is received - * @param correlationId of the RPCRequest that was sent - * @param totalSize only include if this is an OnPutFileUpdateListener. Otherwise it will be ignored. - */ - private void addOnRPCResponseListener(OnRPCResponseListener listener,int correlationId, int totalSize){ - synchronized(ON_UPDATE_LISTENER_LOCK){ - if(rpcResponseListeners!=null - && listener !=null){ - if(listener.getListenerType() == OnRPCResponseListener.UPDATE_LISTENER_TYPE_PUT_FILE){ - ((OnPutFileUpdateListener)listener).setTotalSize(totalSize); - } - listener.onStart(correlationId); - rpcResponseListeners.put(correlationId, listener); - } - } - } - - @SuppressWarnings("unused") - private HashMap<Integer, OnRPCResponseListener> getResponseListeners(){ - synchronized(ON_UPDATE_LISTENER_LOCK){ - return this.rpcResponseListeners; - } - } - - /** - * Retrieves the auth token, if any, that was attached to the StartServiceACK for the RPC - * service from the module. For example, this should be used to login to a user account. - * @return the string representation of the auth token - */ - public String getAuthToken(){ - return this.authToken; - } - - @SuppressWarnings("UnusedReturnValue") - private boolean onRPCNotificationReceived(RPCNotification notification){ - if(notification == null){ - DebugTool.logError("onRPCNotificationReceived - Notification was null"); - return false; - } - DebugTool.logInfo("onRPCNotificationReceived - " + notification.getFunctionName() ); - - //Before updating any listeners, make sure to do any final updates to the notification RPC now - if(FunctionID.ON_HMI_STATUS.toString().equals(notification.getFunctionName())){ - OnHMIStatus onHMIStatus = (OnHMIStatus) notification; - onHMIStatus.setFirstRun(firstTimeFull); - if (onHMIStatus.getHmiLevel() == HMILevel.HMI_FULL) { - firstTimeFull = false; - } - } - - synchronized(ON_NOTIFICATION_LISTENER_LOCK){ - CopyOnWriteArrayList<OnRPCNotificationListener> listeners = rpcNotificationListeners.get(FunctionID.getFunctionId(notification.getFunctionName())); - if(listeners!=null && listeners.size()>0) { - for (OnRPCNotificationListener listener : listeners) { - listener.onNotified(notification); - } - return true; - } - return false; - } - } - - /** - * This will add a listener for the specific type of notification. As of now it will only allow - * a single listener per notification function id - * @param notificationId The notification type that this listener is designated for - * @param listener The listener that will be called when a notification of the provided type is received - */ - @SuppressWarnings("unused") - private void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener){ - synchronized(ON_NOTIFICATION_LISTENER_LOCK){ - if(notificationId != null && listener != null){ - if(!rpcNotificationListeners.containsKey(notificationId.getId())){ - rpcNotificationListeners.put(notificationId.getId(),new CopyOnWriteArrayList<OnRPCNotificationListener>()); - } - rpcNotificationListeners.get(notificationId.getId()).add(listener); - } - } - } - - private boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener){ - synchronized(ON_NOTIFICATION_LISTENER_LOCK){ - if(rpcNotificationListeners!= null - && notificationId != null - && listener != null - && rpcNotificationListeners.containsKey(notificationId.getId())){ - return rpcNotificationListeners.get(notificationId.getId()).remove(listener); - } - } - return false; - } - - @SuppressWarnings("UnusedReturnValue") - private boolean onRPCRequestReceived(RPCRequest request){ - if(request == null){ - DebugTool.logError("onRPCRequestReceived - request was null"); - return false; - } - DebugTool.logInfo("onRPCRequestReceived - " + request.getFunctionName() ); - - synchronized(ON_REQUEST_LISTENER_LOCK){ - CopyOnWriteArrayList<OnRPCRequestListener> listeners = rpcRequestListeners.get(FunctionID.getFunctionId(request.getFunctionName())); - if(listeners!=null && listeners.size()>0) { - for (OnRPCRequestListener listener : listeners) { - listener.onRequest(request); - } - return true; - } - return false; - } - } - - /** - * This will add a listener for the specific type of request. As of now it will only allow - * a single listener per request function id - * @param requestId The request type that this listener is designated for - * @param listener The listener that will be called when a request of the provided type is received - */ - @SuppressWarnings("unused") - private void addOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener){ - synchronized(ON_REQUEST_LISTENER_LOCK){ - if(requestId != null && listener != null){ - if(!rpcRequestListeners.containsKey(requestId.getId())){ - rpcRequestListeners.put(requestId.getId(),new CopyOnWriteArrayList<OnRPCRequestListener>()); - } - rpcRequestListeners.get(requestId.getId()).add(listener); - } - } - } - - @SuppressWarnings("UnusedReturnValue") - private boolean removeOnRPCRequestListener(FunctionID requestId, OnRPCRequestListener listener){ - synchronized(ON_REQUEST_LISTENER_LOCK){ - if(rpcRequestListeners!= null - && requestId != null - && listener != null - && rpcRequestListeners.containsKey(requestId.getId())){ - return rpcRequestListeners.get(requestId.getId()).remove(listener); - } - } - return false; - } - - /* ******************************************************************************************************* - **************************************** RPC LISTENERS !! END !! **************************************** - *********************************************************************************************************/ - - - - private void sendRPCMessagePrivate(RPCMessage message){ - try { - //FIXME this is temporary until the next major release of the library where OK is removed - if (message.getMessageType().equals(RPCMessage.KEY_REQUEST)) { - RPCRequest request = (RPCRequest) message; - if(FunctionID.SUBSCRIBE_BUTTON.toString().equals(request.getFunctionName()) - || FunctionID.UNSUBSCRIBE_BUTTON.toString().equals(request.getFunctionName()) - || FunctionID.BUTTON_PRESS.toString().equals(request.getFunctionName())) { - - ButtonName buttonName = (ButtonName) request.getObject(ButtonName.class, SubscribeButton.KEY_BUTTON_NAME); - - - if (rpcSpecVersion != null) { - if (rpcSpecVersion.getMajor() < 5) { - - if (ButtonName.PLAY_PAUSE.equals(buttonName)) { - request.setParameters(SubscribeButton.KEY_BUTTON_NAME, ButtonName.OK); - } - } else { //Newer than version 5.0.0 - if (ButtonName.OK.equals(buttonName)) { - RPCRequest request2 = new RPCRequest(request); - request2.setParameters(SubscribeButton.KEY_BUTTON_NAME, ButtonName.PLAY_PAUSE); - request2.setOnRPCResponseListener(request.getOnRPCResponseListener()); - sendRPCMessagePrivate(request2); - return; - } - } - } - - } - } - - - message.format(rpcSpecVersion,true); - byte[] msgBytes = JsonRPCMarshaller.marshall(message, (byte)getProtocolVersion().getMajor()); - - final ProtocolMessage pm = new ProtocolMessage(); - pm.setData(msgBytes); - if (session != null){ - pm.setSessionID(session.getSessionId()); - } - - pm.setMessageType(MessageType.RPC); - pm.setSessionType(SessionType.RPC); - pm.setFunctionID(FunctionID.getFunctionId(message.getFunctionName())); - - if (encryptionLifecycleManager != null && encryptionLifecycleManager.isEncryptionReady() && encryptionLifecycleManager.getRPCRequiresEncryption(message.getFunctionID())) { - pm.setPayloadProtected(true); - } else { - pm.setPayloadProtected(message.isPayloadProtected()); - } - if (pm.getPayloadProtected() && (encryptionLifecycleManager == null || !encryptionLifecycleManager.isEncryptionReady())){ - String errorInfo = "Trying to send an encrypted message and there is no secured service"; - if (message.getMessageType().equals((RPCMessage.KEY_REQUEST))) { - RPCRequest request = (RPCRequest) message; - OnRPCResponseListener listener = ((RPCRequest) message).getOnRPCResponseListener(); - if (listener != null) { - listener.onError(request.getCorrelationID(), Result.ABORTED, errorInfo); - } - } - DebugTool.logWarning(errorInfo); - return; - } - - if(RPCMessage.KEY_REQUEST.equals(message.getMessageType())){ // Request Specifics - pm.setRPCType((byte)0x00); - Integer corrId = ((RPCRequest)message).getCorrelationID(); - if( corrId== null) { - Log.e(TAG, "No correlation ID attached to request. Not sending"); - return; - }else{ - pm.setCorrID(corrId); - - OnRPCResponseListener listener = ((RPCRequest)message).getOnRPCResponseListener(); - if(listener != null){ - addOnRPCResponseListener(listener, corrId, msgBytes.length); - } - } - }else if (RPCMessage.KEY_RESPONSE.equals(message.getMessageType())){ // Response Specifics - RPCResponse response = (RPCResponse) message; - pm.setRPCType((byte)0x01); - if (response.getCorrelationID() == null) { - //Log error here - //throw new SdlException("CorrelationID cannot be null. RPC: " + response.getFunctionName(), SdlExceptionCause.INVALID_ARGUMENT); - Log.e(TAG, "No correlation ID attached to response. Not sending"); - return; - } else { - pm.setCorrID(response.getCorrelationID()); - } - }else if (message.getMessageType().equals(RPCMessage.KEY_NOTIFICATION)) { // Notification Specifics - pm.setRPCType((byte)0x02); - } - - if (message.getBulkData() != null){ - pm.setBulkData(message.getBulkData()); - } - - if(message.getFunctionName().equalsIgnoreCase(FunctionID.PUT_FILE.name())){ - pm.setPriorityCoefficient(1); - } - - session.sendMessage(pm); - - } catch (OutOfMemoryError e) { - e.printStackTrace(); - } + @Override + void initializeProxy() { + super.initializeProxy(); + this.session = new SdlSession(sdlConnectionListener, _transportConfig); } - - - /* ******************************************************************************************************* - *************************************** ISdlConnectionListener START ************************************ - *********************************************************************************************************/ - - final ISdlConnectionListener sdlConnectionListener = new ISdlConnectionListener() { - @Override - public void onTransportDisconnected(String info) { + @Override + void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig) { + super.onTransportDisconnected(info, availablePrimary, transportConfig); + if (!availablePrimary) { onClose(info, null); - - } - - @Override - public void onTransportDisconnected(String info, boolean availablePrimary, BaseTransportConfig transportConfig) { - if (!availablePrimary) { - onClose(info, null); - } - - } - - @Override - public void onTransportError(String info, Exception e) { - onClose(info, e); - - } - - @Override - public void onProtocolMessageReceived(ProtocolMessage msg) { - //Incoming message - if (SessionType.RPC.equals(msg.getSessionType()) - || SessionType.BULK_DATA.equals(msg.getSessionType())) { - - RPCMessage rpc = RpcConverter.extractRpc(msg, session.getProtocolVersion()); - if (rpc != null) { - String messageType = rpc.getMessageType(); - Log.v(TAG, "RPC received - " + messageType); - - rpc.format(rpcSpecVersion, true); - - onRPCReceived(rpc); - - if (RPCMessage.KEY_RESPONSE.equals(messageType)) { - - onRPCResponseReceived((RPCResponse) rpc); - - } else if (RPCMessage.KEY_NOTIFICATION.equals(messageType)) { - FunctionID functionID = rpc.getFunctionID(); - if (functionID != null && (functionID.equals(FunctionID.ON_BUTTON_PRESS)) || functionID.equals(FunctionID.ON_BUTTON_EVENT)) { - RPCNotification notificationCompat = handleButtonNotificationFormatting(rpc); - if(notificationCompat != null){ - onRPCNotificationReceived((notificationCompat)); - } - } - - onRPCNotificationReceived((RPCNotification) rpc); - - } else if (RPCMessage.KEY_REQUEST.equals(messageType)) { - - onRPCRequestReceived((RPCRequest) rpc); - - } - } else { - Log.w(TAG, "Shouldn't be here"); - } - } - - } - - @Override - public void onProtocolSessionStartedNACKed(SessionType sessionType, byte sessionID, byte version, String correlationID, List<String> rejectedParams) { - Log.w(TAG, "onProtocolSessionStartedNACKed " + sessionID); - } - - @Override - public void onProtocolSessionStarted(SessionType sessionType, byte sessionID, byte version, String correlationID, int hashID, boolean isEncrypted) { - - Log.i(TAG, "on protocol session started"); - if (sessionType != null) { - if (minimumProtocolVersion != null && minimumProtocolVersion.isNewerThan(getProtocolVersion()) == 1) { - Log.w(TAG, String.format("Disconnecting from head unit, the configured minimum protocol version %s is greater than the supported protocol version %s", minimumProtocolVersion, getProtocolVersion())); - session.endService(sessionType, session.getSessionId()); - cleanProxy(); - return; - } - - if (sessionType.equals(SessionType.RPC)) { - if (appConfig != null) { - - appConfig.prepare(); - - SdlMsgVersion sdlMsgVersion = new SdlMsgVersion(); - sdlMsgVersion.setMajorVersion(MAX_SUPPORTED_RPC_VERSION.getMajor()); - sdlMsgVersion.setMinorVersion(MAX_SUPPORTED_RPC_VERSION.getMinor()); - sdlMsgVersion.setPatchVersion(MAX_SUPPORTED_RPC_VERSION.getPatch()); - - RegisterAppInterface rai = new RegisterAppInterface(sdlMsgVersion, - appConfig.getAppName(), appConfig.isMediaApp(), appConfig.getLanguageDesired(), - appConfig.getHmiDisplayLanguageDesired(), appConfig.getAppID()); - rai.setCorrelationID(REGISTER_APP_INTERFACE_CORRELATION_ID); - - rai.setTtsName(appConfig.getTtsName()); - rai.setNgnMediaScreenAppName(appConfig.getNgnMediaScreenAppName()); - rai.setVrSynonyms(appConfig.getVrSynonyms()); - rai.setAppHMIType(appConfig.getAppType()); - rai.setDayColorScheme(appConfig.getDayColorScheme()); - rai.setNightColorScheme(appConfig.getNightColorScheme()); - - //Add device/system info in the future - //TODO attach previous hash id - - sendRPCMessagePrivate(rai); - } else { - Log.e(TAG, "App config was null, soo..."); - } - - - } else { - lifecycleListener.onServiceStarted(sessionType); - } - } - } - - @Override - public void onProtocolSessionEnded(SessionType sessionType, byte sessionID, String correlationID) { - - } - - @Override - public void onProtocolSessionEndedNACKed(SessionType sessionType, byte sessionID, String correlationID) { - - } - - @Override - public void onProtocolError(String info, Exception e) { - DebugTool.logError("Protocol Error - " + info, e); - } - - @Override - public void onHeartbeatTimedOut(byte sessionID) { /* Deprecated */ } - - @Override - public void onProtocolServiceDataACK(SessionType sessionType, int dataSize, byte sessionID) {/* Unused */ } - - - @Override - public void onAuthTokenReceived(String token, byte sessionID) { - LifecycleManager.this.authToken = token; - } - - }; - /* ******************************************************************************************************* - *************************************** ISdlConnectionListener END ************************************ - *********************************************************************************************************/ - - - /* ******************************************************************************************************* - ******************************************** ISdl - START *********************************************** - *********************************************************************************************************/ - - final ISdl internalInterface = new ISdl() { - @Override - public void start() { - LifecycleManager.this.start(); - } - - @Override - public void stop() { - LifecycleManager.this.stop(); - } - - @Override - public boolean isConnected() { - return LifecycleManager.this.session.getIsConnected(); - } - - @Override - public void addServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { - LifecycleManager.this.session.addServiceListener(serviceType,sdlServiceListener); - } - - @Override - public void removeServiceListener(SessionType serviceType, ISdlServiceListener sdlServiceListener) { - LifecycleManager.this.session.removeServiceListener(serviceType,sdlServiceListener); - - } - - @Override - public void startVideoService(VideoStreamingParameters parameters, boolean encrypted) { - DebugTool.logWarning("startVideoService is not currently implemented"); - - } - - @Override - public void stopVideoService() { - DebugTool.logWarning("stopVideoService is not currently implemented"); - - } - - @Override - public IVideoStreamListener startVideoStream(boolean isEncrypted, VideoStreamingParameters parameters) { - DebugTool.logWarning("startVideoStream is not currently implemented"); - return null; - } - - @Override - public void startAudioService(boolean encrypted, AudioStreamingCodec codec, AudioStreamingParams params) { - DebugTool.logWarning("startAudioService is not currently implemented"); - } - - @Override - public void startAudioService(boolean encrypted) { - DebugTool.logWarning("startAudioService is not currently implemented"); - - } - - @Override - public void stopAudioService() { - DebugTool.logWarning("stopAudioService is not currently implemented"); - } - - @Override - public IAudioStreamListener startAudioStream(boolean isEncrypted, AudioStreamingCodec codec, AudioStreamingParams params) { - DebugTool.logWarning("startAudioStream is not currently implemented"); - return null; - } - - @Override - public void sendRPCRequest(RPCRequest message) { - LifecycleManager.this.sendRPCMessagePrivate(message); - - } - - @Override - public void sendRPC(RPCMessage message) { - if(isConnected()) { - LifecycleManager.this.sendRPCMessagePrivate(message); - } - } - - @Override - public void sendRequests(List<? extends RPCRequest> rpcs, OnMultipleRequestListener listener) { - LifecycleManager.this.sendRPCs(rpcs,listener); - } - - @Override - public void sendRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { - LifecycleManager.this.sendRPCs(rpcs,listener); - } - - @Override - public void sendSequentialRPCs(List<? extends RPCMessage> rpcs, OnMultipleRequestListener listener) { - LifecycleManager.this.sendSequentialRPCs(rpcs,listener); - } - - @Override - public void addOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { - LifecycleManager.this.addOnRPCNotificationListener(notificationId,listener); - } - - @Override - public boolean removeOnRPCNotificationListener(FunctionID notificationId, OnRPCNotificationListener listener) { - return LifecycleManager.this.removeOnRPCNotificationListener(notificationId,listener); - } - - @Override - public void addOnRPCRequestListener(FunctionID notificationId, OnRPCRequestListener listener) { - LifecycleManager.this.addOnRPCRequestListener(notificationId, listener); - } - - @Override - public boolean removeOnRPCRequestListener(FunctionID notificationId, OnRPCRequestListener listener) { - return LifecycleManager.this.removeOnRPCRequestListener(notificationId, listener); - } - - @Override - public void addOnRPCListener(FunctionID responseId, OnRPCListener listener) { - LifecycleManager.this.addRpcListener(responseId,listener); - } - - @Override - public boolean removeOnRPCListener(FunctionID responseId, OnRPCListener listener) { - return LifecycleManager.this.removeOnRPCListener(responseId,listener); - } - - @Override - public Object getCapability(SystemCapabilityType systemCapabilityType) { - if (LifecycleManager.this.systemCapabilityManager != null) { - return LifecycleManager.this.systemCapabilityManager.getCapability(systemCapabilityType); - } else { - return null; - } - } - - @Override - public void getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener) { - if (LifecycleManager.this.systemCapabilityManager != null) { - LifecycleManager.this.systemCapabilityManager.getCapability(systemCapabilityType, scListener); - } - } - - @Override - public Object getCapability(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener scListener, boolean forceUpdate) { - if (LifecycleManager.this.systemCapabilityManager != null) { - return LifecycleManager.this.systemCapabilityManager.getCapability(systemCapabilityType, scListener, forceUpdate); - } else { - return null; - } - } - - @Override - public RegisterAppInterfaceResponse getRegisterAppInterfaceResponse() { - return raiResponse; - } - - @Override - public boolean isCapabilitySupported(SystemCapabilityType systemCapabilityType) { - if (LifecycleManager.this.systemCapabilityManager != null) { - return LifecycleManager.this.systemCapabilityManager.isCapabilitySupported(systemCapabilityType); - } else { - return false; - } - } - - @Override - public void addOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener) { - if (LifecycleManager.this.systemCapabilityManager != null) { - LifecycleManager.this.systemCapabilityManager.addOnSystemCapabilityListener(systemCapabilityType, listener); - } - } - - @Override - public boolean removeOnSystemCapabilityListener(SystemCapabilityType systemCapabilityType, OnSystemCapabilityListener listener) { - if (LifecycleManager.this.systemCapabilityManager != null) { - return LifecycleManager.this.systemCapabilityManager.removeOnSystemCapabilityListener(systemCapabilityType, listener); - } else { - return false; - } - } - - @Override - public boolean isTransportForServiceAvailable(SessionType serviceType) { - return LifecycleManager.this.session.isTransportForServiceAvailable(serviceType); - } - - @Override - public SdlMsgVersion getSdlMsgVersion() { - SdlMsgVersion msgVersion = new SdlMsgVersion(rpcSpecVersion.getMajor(), rpcSpecVersion.getMinor()); - msgVersion.setPatchVersion(rpcSpecVersion.getPatch()); - return msgVersion; - } - - @Override - public Version getProtocolVersion() { - return LifecycleManager.this.getProtocolVersion(); - } - - @Override - public void startRPCEncryption() { - LifecycleManager.this.startRPCEncryption(); - } - }; - - /* ******************************************************************************************************* - ********************************************* ISdl - END ************************************************ - *********************************************************************************************************/ - - public interface LifecycleListener{ - void onProxyConnected(LifecycleManager lifeCycleManager); - void onProxyClosed(LifecycleManager lifeCycleManager, String info, Exception e, SdlDisconnectedReason reason); - void onServiceStarted(SessionType sessionType); - void onServiceEnded(SessionType sessionType); - void onError(LifecycleManager lifeCycleManager, String info, Exception e); - } - - public static class AppConfig{ - private String appID, appName, ngnMediaScreenAppName; - private Vector<TTSChunk> ttsName; - private Vector<String> vrSynonyms; - private boolean isMediaApp = false; - private Language languageDesired, hmiDisplayLanguageDesired; - private Vector<AppHMIType> appType; - private TemplateColorScheme dayColorScheme, nightColorScheme; - private Version minimumProtocolVersion; - private Version minimumRPCVersion; - - private void prepare(){ - if (getNgnMediaScreenAppName() == null) { - setNgnMediaScreenAppName(getAppName()); - } - - if (getLanguageDesired() == null) { - setLanguageDesired(Language.EN_US); - } - - if (getHmiDisplayLanguageDesired() == null) { - setHmiDisplayLanguageDesired(Language.EN_US); - } - - if (getVrSynonyms() == null) { - setVrSynonyms(new Vector<String>()); - getVrSynonyms().add(getAppName()); - } - } - - public String getAppID() { - return appID; - } - - public void setAppID(String appID) { - this.appID = appID; - } - - public String getAppName() { - return appName; - } - - public void setAppName(String appName) { - this.appName = appName; - } - - public String getNgnMediaScreenAppName() { - return ngnMediaScreenAppName; - } - - public void setNgnMediaScreenAppName(String ngnMediaScreenAppName) { - this.ngnMediaScreenAppName = ngnMediaScreenAppName; - } - - public Vector<TTSChunk> getTtsName() { - return ttsName; - } - - public void setTtsName(Vector<TTSChunk> ttsName) { - this.ttsName = ttsName; - } - - public Vector<String> getVrSynonyms() { - return vrSynonyms; - } - - public void setVrSynonyms(Vector<String> vrSynonyms) { - this.vrSynonyms = vrSynonyms; - } - - public boolean isMediaApp() { - return isMediaApp; - } - - public void setMediaApp(boolean mediaApp) { - isMediaApp = mediaApp; - } - - public Language getLanguageDesired() { - return languageDesired; - } - - public void setLanguageDesired(Language languageDesired) { - this.languageDesired = languageDesired; - } - - public Language getHmiDisplayLanguageDesired() { - return hmiDisplayLanguageDesired; - } - - public void setHmiDisplayLanguageDesired(Language hmiDisplayLanguageDesired) { - this.hmiDisplayLanguageDesired = hmiDisplayLanguageDesired; - } - - public Vector<AppHMIType> getAppType() { - return appType; - } - - public void setAppType(Vector<AppHMIType> appType) { - this.appType = appType; - } - - public TemplateColorScheme getDayColorScheme() { - return dayColorScheme; - } - - public void setDayColorScheme(TemplateColorScheme dayColorScheme) { - this.dayColorScheme = dayColorScheme; - } - - public TemplateColorScheme getNightColorScheme() { - return nightColorScheme; - } - - public void setNightColorScheme(TemplateColorScheme nightColorScheme) { - this.nightColorScheme = nightColorScheme; - } - - public Version getMinimumProtocolVersion() { - return minimumProtocolVersion; - } - - /** - * Sets the minimum protocol version that will be permitted to connect. - * If the protocol version of the head unit connected is below this version, - * the app will disconnect with an EndService protocol message and will not register. - * - * @param minimumProtocolVersion a Version object with the minimally accepted Protocol version - */ - public void setMinimumProtocolVersion(Version minimumProtocolVersion) { - this.minimumProtocolVersion = minimumProtocolVersion; - } - - public Version getMinimumRPCVersion() { - return minimumRPCVersion; - } - - /** - * The minimum RPC version that will be permitted to connect. - * If the RPC version of the head unit connected is below this version, an UnregisterAppInterface will be sent. - * - * @param minimumRPCVersion a Version object with the minimally accepted RPC spec version - */ - public void setMinimumRPCVersion(Version minimumRPCVersion) { - this.minimumRPCVersion = minimumRPCVersion; - } - } - - - /** - * Temporary method to bridge the new PLAY_PAUSE and OKAY button functionality with the old - * OK button name. This should be removed during the next major release - * @param notification an RPC message object that should be either an ON_BUTTON_EVENT or ON_BUTTON_PRESS otherwise - * it will be ignored - */ - private RPCNotification handleButtonNotificationFormatting(RPCMessage notification){ - if(FunctionID.ON_BUTTON_EVENT.toString().equals(notification.getFunctionName()) - || FunctionID.ON_BUTTON_PRESS.toString().equals(notification.getFunctionName())){ - - ButtonName buttonName = (ButtonName)notification.getObject(ButtonName.class, OnButtonEvent.KEY_BUTTON_NAME); - ButtonName compatBtnName = null; - - if(rpcSpecVersion != null && rpcSpecVersion.getMajor() >= 5){ - if(ButtonName.PLAY_PAUSE.equals(buttonName)){ - compatBtnName = ButtonName.OK; - } - }else{ // rpc spec version is either null or less than 5 - if(ButtonName.OK.equals(buttonName)){ - compatBtnName = ButtonName.PLAY_PAUSE; - } - } - - try { - if (compatBtnName != null) { //There is a button name that needs to be swapped out - RPCNotification notification2; - //The following is done because there is currently no way to make a deep copy - //of an RPC. Since this code will be removed, it's ugliness is borderline acceptable. - if (notification instanceof OnButtonEvent) { - OnButtonEvent onButtonEvent = new OnButtonEvent(); - onButtonEvent.setButtonEventMode(((OnButtonEvent) notification).getButtonEventMode()); - onButtonEvent.setCustomButtonID(((OnButtonEvent) notification).getCustomButtonID()); - notification2 = onButtonEvent; - } else if (notification instanceof OnButtonPress) { - OnButtonPress onButtonPress = new OnButtonPress(); - onButtonPress.setButtonPressMode(((OnButtonPress) notification).getButtonPressMode()); - onButtonPress.setCustomButtonName(((OnButtonPress) notification).getCustomButtonName()); - notification2 = onButtonPress; - } else { - return null; - } - - notification2.setParameters(OnButtonEvent.KEY_BUTTON_NAME, compatBtnName); - return notification2; - } - }catch (Exception e){ - //Should never get here - } - } - return null; - } - - private void cleanProxy(){ - if (rpcListeners != null) { - rpcListeners.clear(); - } - if (rpcResponseListeners != null) { - rpcResponseListeners.clear(); - } - if (rpcNotificationListeners != null) { - rpcNotificationListeners.clear(); - } - if (rpcRequestListeners != null) { - rpcRequestListeners.clear(); - } - if (session != null && session.getIsConnected()) { - session.close(); - } - if (encryptionLifecycleManager != null){ - encryptionLifecycleManager.dispose(); - } - } - - @Deprecated - public void setSdlSecurityClassList(List<Class<? extends SdlSecurityBase>> list) { - _secList = list; - } - - /** - * Sets the security libraries and a callback to notify caller when there is update to encryption service - * @param secList The list of security class(es) - * @param listener The callback object - */ - public void setSdlSecurity(@NonNull List<Class<? extends SdlSecurityBase>> secList, ServiceEncryptionListener listener) { - this._secList = secList; - this.encryptionLifecycleManager = new EncryptionLifecycleManager(internalInterface, listener); - } - - private void processRaiResponse(RegisterAppInterfaceResponse rai) { - if (rai == null) return; - - this.raiResponse = rai; - - VehicleType vt = rai.getVehicleType(); - if (vt == null) return; - - String make = vt.getMake(); - if (make == null) return; - - if (_secList == null) return; - - SdlSecurityBase sec; - - for (Class<? extends SdlSecurityBase> cls : _secList) { - try { - sec = cls.newInstance(); - } catch (Exception e) { - continue; - } - - if ((sec != null) && (sec.getMakeList() != null)) { - if (sec.getMakeList().contains(make)) { - sec.setAppId(appConfig.getAppID()); - if (session != null) { - session.setSdlSecurity(sec); - sec.handleSdlSession(session); - } - return; - } - } } } } |