summaryrefslogtreecommitdiff
path: root/android/sdl_android/src/main/java/com/smartdevicelink
diff options
context:
space:
mode:
Diffstat (limited to 'android/sdl_android/src/main/java/com/smartdevicelink')
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java15
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/lifecycle/LifecycleManager.java201
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/SubscribeButtonManager.java17
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/protocol/SdlPacket.java115
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java21
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java5
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java180
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java10
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java102
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java13
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java278
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java56
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/util/HttpRequestTask.java211
13 files changed, 960 insertions, 264 deletions
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 2a34dc62e..b993724ba 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
@@ -49,7 +49,6 @@ import com.smartdevicelink.managers.permission.PermissionManager;
import com.smartdevicelink.managers.screen.ScreenManager;
import com.smartdevicelink.managers.video.VideoStreamManager;
import com.smartdevicelink.proxy.rpc.enums.AppHMIType;
-import com.smartdevicelink.proxy.rpc.enums.SdlDisconnectedReason;
import com.smartdevicelink.transport.BaseTransportConfig;
import com.smartdevicelink.transport.MultiplexTransportConfig;
import com.smartdevicelink.transport.enums.TransportType;
@@ -211,25 +210,13 @@ public class SdlManager extends BaseSdlManager {
}
}
- @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();
- }
- }
-
/**
* 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() {
+ public synchronized void dispose() {
if (this.permissionManager != null) {
this.permissionManager.dispose();
}
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
index 777e29f1b..38c8e4c26 100644
--- 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
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019 Livio, Inc.
+ * Copyright (c) 2019-2020 Livio, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
@@ -54,38 +54,29 @@ import com.smartdevicelink.transport.USBTransportConfig;
import com.smartdevicelink.transport.enums.TransportType;
import com.smartdevicelink.util.DebugTool;
+import java.lang.ref.WeakReference;
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;
+ private ISdlServiceListener videoServiceListener;
+ private boolean videoServiceStartResponseReceived = false;
+ private boolean videoServiceStartResponse = false;
+ private WeakReference<Context> contextWeakReference;
public LifecycleManager(AppConfig appConfig, BaseTransportConfig config, LifecycleListener listener) {
super(appConfig, config, listener);
}
@Override
- void initializeProxy() {
- super.initializeProxy();
+ void initialize() {
+ super.initialize();
//Handle legacy USB connections
if (_transportConfig != null && TransportType.USB.equals(_transportConfig.getTransportType())) {
@@ -112,30 +103,38 @@ public class LifecycleManager extends BaseLifecycleManager {
}
}
- private void cycleProxy(SdlDisconnectedReason disconnectedReason) {
- cleanProxy();
- initializeProxy();
+ @Override
+ void cycle(SdlDisconnectedReason disconnectedReason) {
+ clean();
+ initialize();
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));
+ onClose("Sdl Proxy Cycled", new SdlException("Sdl Proxy Cycled", SdlExceptionCause.SDL_PROXY_CYCLED), disconnectedReason);
}
- try {
- session.startSession();
- } catch (SdlException e) {
- e.printStackTrace();
+ if (session != null) {
+ try {
+ session.startSession();
+ } catch (SdlException e) {
+ e.printStackTrace();
+ }
}
}
@RestrictTo(RestrictTo.Scope.LIBRARY)
public void setContext(Context context) {
- this.context = context;
+ this.contextWeakReference = new WeakReference<>(context);
}
@Override
void setSdlSecurityStaticVars() {
super.setSdlSecurityStaticVars();
+ Context context = null;
Service service = null;
+
+ if(this.contextWeakReference != null){
+ context = contextWeakReference.get();
+ }
if (context != null && context instanceof Service) {
service = (Service) context;
}
@@ -144,11 +143,11 @@ public class LifecycleManager extends BaseLifecycleManager {
}
@Override
- void onProtocolSessionStarted(SessionType sessionType) {
- super.onProtocolSessionStarted(sessionType);
+ void onServiceStarted(SessionType sessionType) {
+ super.onServiceStarted(sessionType);
if (sessionType.eq(SessionType.NAV)) {
- navServiceStartResponseReceived = true;
- navServiceStartResponse = true;
+ videoServiceStartResponseReceived = true;
+ videoServiceStartResponse = true;
}
}
@@ -158,42 +157,18 @@ public class LifecycleManager extends BaseLifecycleManager {
if (availablePrimary) {
_transportConfig = transportConfig;
Log.d(TAG, "notifying RPC session ended, but potential primary transport available");
- cycleProxy(SdlDisconnectedReason.PRIMARY_TRANSPORT_CYCLE_REQUEST);
+ cycle(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;
+ onClose(info, null, null);
}
}
@Override
- void onProtocolSessionEnded(SessionType sessionType) {
- super.onProtocolSessionEnded(sessionType);
+ void onStartServiceNACKed(SessionType sessionType) {
+ super.onStartServiceNACKed(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;
+ videoServiceStartResponseReceived = true;
+ videoServiceStartResponse = false;
}
}
@@ -230,72 +205,60 @@ public class LifecycleManager extends BaseLifecycleManager {
* 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) {
+ private void tryStartVideoStream(boolean isEncrypted, VideoStreamingParameters parameters) {
if (session == null) {
DebugTool.logWarning("SdlSession is not created yet.");
- return null;
+ return;
}
if (getProtocolVersion() != null && getProtocolVersion().getMajor() >= 5 && !systemCapabilityManager.isCapabilitySupported(SystemCapabilityType.VIDEO_STREAMING)) {
DebugTool.logWarning("Module doesn't support video streaming.");
- return null;
+ return;
}
if (parameters == null) {
DebugTool.logWarning("Video parameters were not supplied.");
- return null;
+ return;
}
- 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
+
+ if (!videoServiceStartResponseReceived || !videoServiceStartResponse //If we haven't started the service before
+ || (videoServiceStartResponse && 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;
+ videoServiceStartResponseReceived = false;
+ videoServiceStartResponse = false;
+ addVideoServiceListener();
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() {
+ private void addVideoServiceListener() {
// videos may be started and stopped. Only add this once
- if (navServiceListener == null) {
+ if (videoServiceListener == null) {
- navServiceListener = new ISdlServiceListener() {
+ videoServiceListener = new ISdlServiceListener() {
@Override
public void onServiceStarted(SdlSession session, SessionType type, boolean isEncrypted) {
+ videoServiceStartResponseReceived = true;
+ videoServiceStartResponse = true;
}
@Override
public void onServiceEnded(SdlSession session, SessionType type) {
// reset nav flags so nav can start upon the next transport connection
- navServiceStartResponseReceived = false;
- navServiceStartResponse = false;
+ videoServiceStartResponseReceived = false;
+ videoServiceStartResponse = 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;
+ videoServiceStartResponseReceived = false;
+ videoServiceStartResponse = false;
}
};
- session.addServiceListener(SessionType.NAV, navServiceListener);
+ session.addServiceListener(SessionType.NAV, videoServiceListener);
}
}
@@ -305,29 +268,17 @@ public class LifecycleManager extends BaseLifecycleManager {
* @return true if the video service is closed successfully, return false otherwise
*/
@Override
- boolean endVideoStream() {
+ void endVideoStream() {
if (session == null) {
DebugTool.logWarning("SdlSession is not created yet.");
- return false;
+ return;
}
if (!session.getIsConnected()) {
DebugTool.logWarning("Connection is not available.");
- return false;
+ return;
}
- 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
@@ -349,46 +300,16 @@ public class LifecycleManager extends BaseLifecycleManager {
* @return true if the audio service is closed successfully, return false otherwise
*/
@Override
- boolean endAudioStream() {
+ void endAudioStream() {
if (session == null) {
DebugTool.logWarning("SdlSession is not created yet.");
- return false;
+ return;
}
if (!session.getIsConnected()) {
DebugTool.logWarning("Connection is not available.");
- return false;
+ return;
}
- 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/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/SubscribeButtonManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/SubscribeButtonManager.java
new file mode 100644
index 000000000..f38cf79d0
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/managers/screen/SubscribeButtonManager.java
@@ -0,0 +1,17 @@
+package com.smartdevicelink.managers.screen;
+
+import android.support.annotation.NonNull;
+import com.smartdevicelink.proxy.interfaces.ISdl;
+
+/**
+ * <strong>SubscribeButtonManager</strong> <br>
+ *
+ * Note: This class must be accessed through the SdlManager. Do not instantiate it by itself. <br>
+ *
+ */
+class SubscribeButtonManager extends BaseSubscribeButtonManager {
+
+ SubscribeButtonManager(@NonNull ISdl internalInterface) {
+ super(internalInterface);
+ }
+}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/protocol/SdlPacket.java b/android/sdl_android/src/main/java/com/smartdevicelink/protocol/SdlPacket.java
new file mode 100644
index 000000000..7a6292505
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/protocol/SdlPacket.java
@@ -0,0 +1,115 @@
+package com.smartdevicelink.protocol;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.smartdevicelink.transport.utl.TransportRecord;
+import com.smartdevicelink.util.DebugTool;
+
+public class SdlPacket extends BaseSdlPacket implements Parcelable {
+ private static final int EXTRA_PARCEL_DATA_LENGTH = 24;
+
+ public SdlPacket(int version, boolean encryption, int frameType,
+ int serviceType, int frameInfo, int sessionId,
+ int dataSize, int messageId, byte[] payload) {
+ super(version, encryption, frameType, serviceType, frameInfo, sessionId, dataSize, messageId, payload);
+ }
+
+ public SdlPacket(int version, boolean encryption, int frameType,
+ int serviceType, int frameInfo, int sessionId,
+ int dataSize, int messageId, byte[] payload, int offset, int bytesToWrite) {
+ super(version, encryption, frameType, serviceType, frameInfo, sessionId, dataSize, messageId, payload, offset, bytesToWrite);
+ }
+
+ protected SdlPacket() {
+ super();
+ }
+
+ protected SdlPacket(BaseSdlPacket packet) {
+ super(packet);
+ }
+
+ /* ***************************************************************************************************************************************************
+ * *********************************************************** Parceable Overrides *****************************************************************
+ *****************************************************************************************************************************************************/
+
+
+
+ //I think this is FIFO...right?
+ public SdlPacket(Parcel p) {
+ this.version = p.readInt();
+ this.encryption = (p.readInt() == 0) ? false : true;
+ this.frameType = p.readInt();
+ this.serviceType = p.readInt();
+ this.frameInfo = p.readInt();
+ this.sessionId = p.readInt();
+ this.dataSize = p.readInt();
+ this.messageId = p.readInt();
+ if(p.readInt() == 1){ //We should have a payload attached
+ payload = new byte[dataSize];
+ p.readByteArray(payload);
+ }
+
+ this.priorityCoefficient = p.readInt();
+
+ if(p.dataAvail() > EXTRA_PARCEL_DATA_LENGTH) { //See note on constant for why not 0
+ try {
+ messagingVersion = p.readInt();
+ if (messagingVersion >= 2) {
+ if (p.readInt() == 1) { //We should have a transport type attached
+ this.transportRecord = (TransportRecord) p.readParcelable(TransportRecord.class.getClassLoader());
+ }
+ }
+ }catch (RuntimeException e){
+ DebugTool.logError("Error creating packet from parcel", e);
+ }
+ }
+ }
+
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+
+ dest.writeInt(version);
+ dest.writeInt(encryption? 1 : 0);
+ dest.writeInt(frameType);
+ dest.writeInt(serviceType);
+ dest.writeInt(frameInfo);
+ dest.writeInt(sessionId);
+ dest.writeInt(dataSize);
+ dest.writeInt(messageId);
+ dest.writeInt(payload!=null? 1 : 0);
+ if(payload!=null){
+ dest.writeByteArray(payload);
+ }
+ dest.writeInt(priorityCoefficient);
+
+ ///Additions after initial creation
+ if(messagingVersion > 1){
+ dest.writeInt(messagingVersion);
+
+ dest.writeInt(transportRecord!=null? 1 : 0);
+ if(transportRecord != null){
+ dest.writeParcelable(transportRecord,0);
+ }
+ }
+
+ }
+
+ public static final Parcelable.Creator<SdlPacket> CREATOR = new Parcelable.Creator<SdlPacket>() {
+ public SdlPacket createFromParcel(Parcel in) {
+ return new SdlPacket(in);
+ }
+
+ @Override
+ public SdlPacket[] newArray(int size) {
+ return new SdlPacket[size];
+ }
+
+ };
+}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
index b0bda2d26..b42360019 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/proxy/SdlProxyBase.java
@@ -50,6 +50,7 @@ import android.view.InputDevice;
import android.view.MotionEvent;
import android.view.Surface;
+import com.livio.taskmaster.Taskmaster;
import com.smartdevicelink.BuildConfig;
import com.smartdevicelink.Dispatcher.IDispatchingStrategy;
import com.smartdevicelink.Dispatcher.ProxyMessageDispatcher;
@@ -313,6 +314,7 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
private Set<String> encryptionRequiredRPCs = new HashSet<>();
private boolean rpcSecuredServiceStarted;
private ServiceEncryptionListener serviceEncryptionListener;
+ private Taskmaster taskmaster;
// Interface broker
private SdlInterfaceBroker _interfaceBroker = null;
@@ -537,7 +539,23 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
public void startRPCEncryption() {
SdlProxyBase.this.startProtectedRPCService();
}
+
+ @Override
+ public Taskmaster getTaskmaster() {
+ return SdlProxyBase.this.getTaskmaster();
+ }
};
+
+ Taskmaster getTaskmaster() {
+ if (taskmaster == null) {
+ Taskmaster.Builder builder = new Taskmaster.Builder();
+ builder.setThreadCount(2);
+ builder.shouldBeDaemon(false);
+ taskmaster = builder.build();
+ taskmaster.start();
+ }
+ return taskmaster;
+ }
private void notifyPutFileStreamError(Exception e, String info)
{
@@ -1832,6 +1850,9 @@ public abstract class SdlProxyBase<proxyListenerType extends IProxyListenerBase>
public void dispose() throws SdlException {
SdlTrace.logProxyEvent("Application called dispose() method.", SDL_LIB_TRACE_KEY);
disposeInternal(SdlDisconnectedReason.APPLICATION_REQUESTED_DISCONNECT);
+ if (taskmaster != null) {
+ taskmaster.shutdown();
+ }
}
/**
* Terminates the App's Interface Registration, closes the transport connection, ends the protocol session, and frees any resources used by the proxy.
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java
index cc7b030c7..ba34a3f58 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexBluetoothTransport.java
@@ -35,6 +35,7 @@ import android.util.Log;
import com.smartdevicelink.protocol.SdlPacket;
import com.smartdevicelink.transport.enums.TransportType;
import com.smartdevicelink.transport.utl.TransportRecord;
+import com.smartdevicelink.util.DebugTool;
import java.io.IOException;
import java.io.InputStream;
@@ -815,7 +816,9 @@ public class MultiplexBluetoothTransport extends MultiplexBaseTransport{
}
} catch (IOException|NullPointerException e) { // NPE is ONLY to catch error on mmInStream
Log.e(TAG, "Lost connection in the Connected Thread");
- e.printStackTrace();
+ if(DebugTool.isDebugEnabled()){
+ e.printStackTrace();
+ }
connectionLost();
break;
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
index ba1cc80a0..3baabc74a 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlBroadcastReceiver.java
@@ -53,6 +53,7 @@ import android.util.Log;
import com.smartdevicelink.R;
import com.smartdevicelink.transport.RouterServiceValidator.TrustedListCallback;
import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.SdlDeviceListener;
import com.smartdevicelink.util.AndroidTools;
import com.smartdevicelink.util.DebugTool;
import com.smartdevicelink.util.SdlAppInfo;
@@ -85,6 +86,8 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
private static final Object QUEUED_SERVICE_LOCK = new Object();
private static ComponentName queuedService = null;
private static Thread.UncaughtExceptionHandler foregroundExceptionHandler = null;
+ private static final Object DEVICE_LISTENER_LOCK = new Object();
+ private static SdlDeviceListener sdlDeviceListener;
public int getRouterServiceVersion(){
return SdlRouterService.ROUTER_SERVICE_VERSION_NUMBER;
@@ -190,9 +193,6 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
});
}
- }else{
- //This was previously not hooked up, so let's leave it commented out
- //onSdlDisabled(context);
}
return;
}else if(intent.getBooleanExtra(TransportConstants.PING_ROUTER_SERVICE_EXTRA, false)){
@@ -223,8 +223,57 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
}
}
+ /**
+ * This method will attempt to start the router service.
+ * @param context to be used to start the service and send broadcasts
+ * @param componentName the router service that should be started
+ * @param altTransportWake if the alt transport flag should be set. Only used in debug
+ * @param device the connected bluetooth device
+ */
+ private static void startRouterService(Context context, ComponentName componentName, boolean altTransportWake, BluetoothDevice device, boolean confirmedDevice) {
+ if (componentName == null) {
+ return;
+ }
+
+ Intent serviceIntent = new Intent();
+ serviceIntent.setComponent(componentName);
+
+ if (altTransportWake) {
+ serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT);
+ }
+
+ if (device != null) {
+ serviceIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+ }
+
+ if (confirmedDevice) {
+ serviceIntent.putExtra(TransportConstants.CONFIRMED_SDL_DEVICE, confirmedDevice);
+ }
+
+ try {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ context.startService(serviceIntent);
+ } else {
+ serviceIntent.putExtra(FOREGROUND_EXTRA, true);
+ DebugTool.logInfo("Attempting to startForegroundService - " + System.currentTimeMillis());
+ setForegroundExceptionHandler(); //Prevent ANR in case the OS takes too long to start the service
+ context.startForegroundService(serviceIntent);
+
+ }
+ //Make sure to send this out for old apps to close down
+ SdlRouterService.LocalRouterService self = SdlRouterService.getLocalRouterService(serviceIntent, serviceIntent.getComponent());
+ Intent restart = new Intent(SdlRouterService.REGISTER_NEWER_SERVER_INSTANCE_ACTION);
+ restart.putExtra(LOCAL_ROUTER_SERVICE_EXTRA, self);
+ restart.putExtra(LOCAL_ROUTER_SERVICE_DID_START_OWN, true);
+ context.sendBroadcast(restart);
+
+ } catch (SecurityException e) {
+ Log.e(TAG, "Security exception, process is bad");
+ }
+ }
+
private boolean wakeUpRouterService(final Context context, final boolean ping, final boolean altTransportWake, final BluetoothDevice device){
- new ServiceFinder(context, context.getPackageName(), new ServiceFinder.ServiceFinderCallback() {
+ new ServiceFinder(context, context.getPackageName(), new ServiceFinder.ServiceFinderCallback() {
@Override
public void onComplete(Vector<ComponentName> routerServices) {
runningBluetoothServicePackage = new Vector<ComponentName>();
@@ -232,43 +281,43 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
if (runningBluetoothServicePackage.isEmpty()) {
//If there isn't a service running we should try to start one
//We will try to sort the SDL enabled apps and find the one that's been installed the longest
- Intent serviceIntent;
- List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator());
+ final List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator());
+ synchronized (DEVICE_LISTENER_LOCK) {
+ final boolean sdlDeviceListenerEnabled = SdlDeviceListener.isFeatureSupported(sdlAppInfoList);
+ if (sdlDeviceListenerEnabled) {
+ String myPackage = context.getPackageName();
+ String routerServicePackage = null;
+ if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty() && sdlAppInfoList.get(0).getRouterServiceComponentName() != null) {
+ routerServicePackage = sdlAppInfoList.get(0).getRouterServiceComponentName().getPackageName();
+ }
+ DebugTool.logInfo(TAG + ": This app's package: " + myPackage);
+ DebugTool.logInfo(TAG + ": Router service app's package: " + routerServicePackage);
+ if (myPackage != null && myPackage.equalsIgnoreCase(routerServicePackage)) {
+ SdlDeviceListener sdlDeviceListener = getSdlDeviceListener(context, device);
+ if (!sdlDeviceListener.isRunning()) {
+ sdlDeviceListener.start();
+ }
+ } else {
+ DebugTool.logInfo(TAG + ": Not the app to start the router service nor device listener");
+ }
+ return;
+ }
+ }
+
if (sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
- serviceIntent = new Intent();
- serviceIntent.setComponent(sdlAppInfoList.get(0).getRouterServiceComponentName());
+ startRouterService(context, sdlAppInfoList.get(0).getRouterServiceComponentName(), altTransportWake, device, false);
} else{
Log.d(TAG, "No SDL Router Services found");
Log.d(TAG, "WARNING: This application has not specified its SdlRouterService correctly in the manifest. THIS WILL THROW AN EXCEPTION IN FUTURE RELEASES!!");
return;
}
- if (altTransportWake) {
- serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT);
- }
- if(device != null){
- serviceIntent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
- }
- try {
- if(Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
- context.startService(serviceIntent);
- }else {
- serviceIntent.putExtra(FOREGROUND_EXTRA, true);
- DebugTool.logInfo("Attempting to startForegroundService - " + System.currentTimeMillis());
- setForegroundExceptionHandler(); //Prevent ANR in case the OS takes too long to start the service
- context.startForegroundService(serviceIntent);
+ } else { //There are currently running services
+ if(DebugTool.isDebugEnabled()){
+ for(ComponentName service : runningBluetoothServicePackage){
+ DebugTool.logInfo("Currently running router service: " + service.getPackageName());
}
- //Make sure to send this out for old apps to close down
- SdlRouterService.LocalRouterService self = SdlRouterService.getLocalRouterService(serviceIntent, serviceIntent.getComponent());
- Intent restart = new Intent(SdlRouterService.REGISTER_NEWER_SERVER_INSTANCE_ACTION);
- restart.putExtra(LOCAL_ROUTER_SERVICE_EXTRA, self);
- restart.putExtra(LOCAL_ROUTER_SERVICE_DID_START_OWN, true);
- context.sendBroadcast(restart);
-
- } catch (SecurityException e) {
- Log.e(TAG, "Security exception, process is bad");
}
- } else {
if (altTransportWake) {
wakeRouterServiceAltTransport(context);
return;
@@ -281,7 +330,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
}
}
});
- return true;
+ return true;
}
private void wakeRouterServiceAltTransport(Context context){
@@ -289,7 +338,11 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
serviceIntent.setAction(TransportConstants.BIND_REQUEST_TYPE_ALT_TRANSPORT);
for (ComponentName compName : runningBluetoothServicePackage) {
serviceIntent.setComponent(compName);
- context.startService(serviceIntent);
+ try{
+ context.startService(serviceIntent);
+ } catch (Exception e){
+ DebugTool.logError("Can't start router service for alt transport");
+ }
}
}
@@ -329,10 +382,9 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
* Determines if an instance of the Router Service is currently running on the device.<p>
* <b>Note:</b> This method no longer works on Android Oreo or newer
* @param context A context to access Android system services through.
- * @param pingService Set this to true if you want to make sure the service is up and listening to bluetooth
* @return True if a SDL Router Service is currently running, false otherwise.
*/
- private static boolean isRouterServiceRunning(Context context, boolean pingService){
+ private static boolean isRouterServiceRunning(Context context){
if(context == null){
Log.e(TAG, "Can't look for router service, context supplied was null");
return false;
@@ -356,9 +408,6 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
//Log.d(TAG, "Found Service: "+ service.service.getClassName());
if ((service.service.getClassName()).toLowerCase(Locale.US).contains(SDL_ROUTER_SERVICE_CLASS_NAME) && AndroidTools.isServiceExported(context, service.service)) {
runningBluetoothServicePackage.add(service.service); //Store which instance is running
- if (pingService) {
- pingRouterService(context, service.service.getPackageName(), service.service.getClassName());
- }
}
}
return runningBluetoothServicePackage.size() > 0;
@@ -366,7 +415,8 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
}
/**
- * Attempts to ping a running router service
+ * Attempts to ping a running router service. It does call startForegroundService so it is
+ * important to only call this as a ping if the service is already started.
* @param context A context to access Android system services through.
* @param packageName Package name for service to ping
* @param className Class name for service to ping
@@ -431,7 +481,7 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
}
return;
}
- if((!lookForServices || isRouterServiceRunning(context,false)) && !runningBluetoothServicePackage.isEmpty()){ //So there is a service up, let's see if it's connected
+ if((!lookForServices || isRouterServiceRunning(context)) && !runningBluetoothServicePackage.isEmpty()){ //So there is a service up, let's see if it's connected
final ConcurrentLinkedQueue<ComponentName> list = new ConcurrentLinkedQueue<ComponentName>(runningBluetoothServicePackage);
final SdlRouterStatusProvider.ConnectedStatusCallback sdlBrCallback = new SdlRouterStatusProvider.ConnectedStatusCallback() {
@@ -471,12 +521,13 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
}else{
Log.w(TAG, "Router service isn't running, returning false.");
if(isBluetoothConnected()){
- Log.d(TAG, "Bluetooth is connected. Attempting to start Router Service");
+ Log.d(TAG, "Bluetooth is connected. Attempting to ping Router Service");
Intent serviceIntent = new Intent();
serviceIntent.setAction(TransportConstants.START_ROUTER_SERVICE_ACTION);
serviceIntent.putExtra(TransportConstants.PING_ROUTER_SERVICE_EXTRA, true);
- AndroidTools.sendExplicitBroadcast(context,serviceIntent,null);
+ AndroidTools.sendExplicitBroadcast(context, serviceIntent,null);
}
+
if(callback!=null){
callback.onConnectionStatusUpdate(false, null,context);
}
@@ -500,6 +551,49 @@ public abstract class SdlBroadcastReceiver extends BroadcastReceiver{
return false;
}
+
+ private static SdlDeviceListener getSdlDeviceListener(Context context, BluetoothDevice bluetoothDevice){
+
+ synchronized (DEVICE_LISTENER_LOCK){
+ if (sdlDeviceListener == null){
+ sdlDeviceListener = new SdlDeviceListener(context, bluetoothDevice, new SdlDeviceListener.Callback() {
+ @Override
+ public boolean onTransportConnected(Context context, BluetoothDevice bluetoothDevice) {
+
+ synchronized (DEVICE_LISTENER_LOCK){
+ sdlDeviceListener = null;
+ if(context != null) {
+ final List<SdlAppInfo> sdlAppInfoList = AndroidTools.querySdlAppInfo(context, new SdlAppInfo.BestRouterComparator());
+ if(sdlAppInfoList != null && !sdlAppInfoList.isEmpty()) {
+ ComponentName routerService = sdlAppInfoList.get(0).getRouterServiceComponentName();
+ startRouterService(context, routerService, false, bluetoothDevice, true);
+ }
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public void onTransportDisconnected(BluetoothDevice bluetoothDevice) {
+ synchronized (DEVICE_LISTENER_LOCK){
+ sdlDeviceListener = null;
+ }
+ }
+
+ @Override
+ public void onTransportError(BluetoothDevice bluetoothDevice) {
+ synchronized (DEVICE_LISTENER_LOCK){
+ sdlDeviceListener = null;
+ }
+ }
+ });
+ }
+ }
+
+ return sdlDeviceListener;
+ }
+
public static ComponentName consumeQueuedRouterService(){
synchronized(QUEUED_SERVICE_LOCK){
ComponentName retVal = queuedService;
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
index 4edc639e8..2e3317661 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/SdlRouterService.java
@@ -140,7 +140,7 @@ public class SdlRouterService extends Service{
/**
* <b> NOTE: DO NOT MODIFY THIS UNLESS YOU KNOW WHAT YOU'RE DOING.</b>
*/
- protected static final int ROUTER_SERVICE_VERSION_NUMBER = 12;
+ protected static final int ROUTER_SERVICE_VERSION_NUMBER = 13;
private static final String ROUTER_SERVICE_PROCESS = "com.smartdevicelink.router";
@@ -1259,7 +1259,9 @@ public class SdlRouterService extends Service{
address = device.getAddress();
}
}
- int timeout = getNotificationTimeout(address);
+ boolean confirmedDevice = intent.getBooleanExtra(TransportConstants.CONFIRMED_SDL_DEVICE, false);
+ int timeout = getNotificationTimeout(address, confirmedDevice);
+
enterForeground("Waiting for connection...", timeout, false);
resetForegroundTimeOut(timeout);
} else {
@@ -1383,9 +1385,9 @@ public class SdlRouterService extends Service{
* @return the amount of time for a timeout handler to remove the notification.
*/
@SuppressLint("MissingPermission")
- private int getNotificationTimeout(String address){
+ private int getNotificationTimeout(String address, boolean confirmedDevice){
if(address != null){
- if(hasSDLConnected(address)){
+ if(confirmedDevice || hasSDLConnected(address)){
return FOREGROUND_TIMEOUT * 2;
}else if(this.isFirstStatusCheck(address)) {
// If this is the first time the service has ever connected to this device we want
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java
index 710e9d610..003556109 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportBroker.java
@@ -86,8 +86,7 @@ public class TransportBroker {
private Context currentContext = null;
private final Object INIT_LOCK = new Object();
-
- private TransportType queuedOnTransportConnect = null;
+ private final Object MESSAGE_SEND_LOCK = new Object();
Messenger routerServiceMessenger = null;
final Messenger clientMessenger;
@@ -126,59 +125,55 @@ public class TransportBroker {
};
}
- protected synchronized boolean sendMessageToRouterService(Message message) {
+ protected boolean sendMessageToRouterService(Message message) {
return sendMessageToRouterService(message, 0);
}
- protected synchronized boolean sendMessageToRouterService(Message message, int retryCount) {
- if (message == null) {
- Log.w(TAG, "Attempted to send null message");
- return false;
- }
- //Log.i(TAG, "Attempting to send message type - " + message.what);
- if (isBound && routerServiceMessenger != null) {
- if (registeredWithRouterService
- || message.what == TransportConstants.ROUTER_REGISTER_CLIENT) { //We can send a message if we are registered or are attempting to register
- try {
- routerServiceMessenger.send(message);
- return true;
- } catch (RemoteException e) {
- e.printStackTrace();
- //Let's check to see if we should retry
- if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && e instanceof TransactionTooLargeException )
- || (retryCount < 5 && routerServiceMessenger.getBinder().isBinderAlive() && routerServiceMessenger.getBinder().pingBinder())) { //We probably just failed on a small transaction =\
- try {
- Thread.sleep(100);
- } catch (InterruptedException e1) {
- e1.printStackTrace();
+ protected boolean sendMessageToRouterService(Message message, int retryCount) {
+ synchronized (MESSAGE_SEND_LOCK) {
+ if (message == null) {
+ Log.w(TAG, "Attempted to send null message");
+ return false;
+ }
+ //Log.i(TAG, "Attempting to send message type - " + message.what);
+ if (isBound && routerServiceMessenger != null && routerServiceMessenger.getBinder() != null && routerServiceMessenger.getBinder().isBinderAlive()) {
+ if (registeredWithRouterService
+ || message.what == TransportConstants.ROUTER_REGISTER_CLIENT) { //We can send a message if we are registered or are attempting to register
+ try {
+ routerServiceMessenger.send(message);
+ return true;
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ //Let's check to see if we should retry
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1 && e instanceof TransactionTooLargeException)
+ || (retryCount < 5 && routerServiceMessenger.getBinder().isBinderAlive() && routerServiceMessenger.getBinder().pingBinder())) { //We probably just failed on a small transaction =\
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e1) {
+ e1.printStackTrace();
+ }
+ return sendMessageToRouterService(message, retryCount++);
+ } else {
+ //DeadObject, time to kill our connection
+ Log.d(TAG, "Dead object while attempting to send packet");
+ stop();
+ onHardwareDisconnected(null, null);
+ return false;
}
- return sendMessageToRouterService(message, retryCount++);
- } else {
- //DeadObject, time to kill our connection
- Log.d(TAG, "Dead object while attempting to send packet");
- routerServiceMessenger = null;
- registeredWithRouterService = false;
- unBindFromRouterService();
- isBound = false;
+ } catch (NullPointerException e) {
+ Log.d(TAG, "Null messenger while attempting to send packet"); // NPE, routerServiceMessenger is null
+ stop();
onHardwareDisconnected(null, null);
return false;
}
- } catch (NullPointerException e) {
- Log.d(TAG, "Null messenger while attempting to send packet"); // NPE, routerServiceMessenger is null
- routerServiceMessenger = null;
- registeredWithRouterService = false;
- unBindFromRouterService();
- isBound = false;
- onHardwareDisconnected(null, null);
+ } else {
+ Log.e(TAG, "Unable to send message to router service. Not registered.");
return false;
}
} else {
- Log.e(TAG, "Unable to send message to router service. Not registered.");
+ Log.e(TAG, "Unable to send message to router service. Not bound.");
return false;
}
- } else {
- Log.e(TAG, "Unable to send message to router service. Not bound.");
- return false;
}
}
@@ -416,7 +411,6 @@ public class TransportBroker {
}
//this.appId = appId.concat(timeStamp);
this.appId = appId;
- queuedOnTransportConnect = null;
currentContext = context;
//Log.d(TAG, "Registering our reply receiver: " + whereToReply);
this.routerService = service;
@@ -448,7 +442,6 @@ public class TransportBroker {
synchronized (INIT_LOCK) {
unregisterWithRouterService();
routerServiceMessenger = null;
- queuedOnTransportConnect = null;
unBindFromRouterService();
isBound = false;
}
@@ -463,13 +456,12 @@ public class TransportBroker {
unregisterWithRouterService();
unBindFromRouterService();
routerServiceMessenger = null;
- queuedOnTransportConnect = null;
currentContext = null;
}
}
- private synchronized void unBindFromRouterService() {
+ private void unBindFromRouterService() {
try {
getContext().unbindService(routerConnection);
@@ -487,27 +479,17 @@ public class TransportBroker {
public void onServiceUnregsiteredFromRouterService(int unregisterCode) {
- queuedOnTransportConnect = null;
}
@Deprecated
public void onHardwareDisconnected(TransportType type) {
- routerServiceDisconnect();
+ stop();
}
public void onHardwareDisconnected(TransportRecord record, List<TransportRecord> connectedTransports) {
}
- private void routerServiceDisconnect() {
- synchronized (INIT_LOCK) {
- unBindFromRouterService();
- routerServiceMessenger = null;
- routerConnection = null;
- queuedOnTransportConnect = null;
- }
- }
-
/**
* WILL NO LONGER BE CALLED
*
@@ -518,7 +500,6 @@ public class TransportBroker {
public boolean onHardwareConnected(TransportType type) {
synchronized (INIT_LOCK) {
if (routerServiceMessenger == null) {
- queuedOnTransportConnect = type;
return false;
}
return true;
@@ -528,7 +509,6 @@ public class TransportBroker {
public boolean onHardwareConnected(List<TransportRecord> transports) {
synchronized (INIT_LOCK) {
if (routerServiceMessenger == null && transports != null && transports.size() > 0) {
- queuedOnTransportConnect = transports.get(transports.size() - 1).getType();
return false;
}
return true;
@@ -616,7 +596,7 @@ public class TransportBroker {
ByteArrayMessageSpliter splitter = new ByteArrayMessageSpliter(appId, TransportConstants.ROUTER_SEND_PACKET, bytes, packet.getPrioirtyCoefficient());
splitter.setRouterServiceVersion(routerServiceVersion);
splitter.setTransportRecord(packet.getTransportRecord());
- while (splitter.isActive()) {
+ while (splitter.isActive() && routerServiceMessenger != null) {
sendMessageToRouterService(splitter.nextMessage());
}
return splitter.close();
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
index 1a0951503..459a221f9 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/TransportManager.java
@@ -49,6 +49,7 @@ import android.util.Log;
import com.smartdevicelink.protocol.SdlPacket;
import com.smartdevicelink.protocol.enums.ControlFrameTags;
import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.transport.utl.SdlDeviceListener;
import com.smartdevicelink.transport.utl.TransportRecord;
import com.smartdevicelink.util.DebugTool;
@@ -100,7 +101,7 @@ public class TransportManager extends TransportManagerBase{
if (valid) {
mConfig.service = name;
transport = new TransportBrokerImpl(contextWeakReference.get(), mConfig.appId, mConfig.service);
- DebugTool.logInfo("TransportManager start got called; transport=" + transport);
+ DebugTool.logInfo("TransportManager start was called; transport = " + transport);
if(transport != null){
transport.start();
}
@@ -302,6 +303,16 @@ public class TransportManager extends TransportManagerBase{
transportStatus.clear();
transportStatus.addAll(transports);
}
+ //If a bluetooth device has connected, make sure to save the mac address in the case
+ //this app is asked to host the router service, the app knows to do so immediately on connection.
+ if(transports != null && transports.size() > 0) {
+ for (TransportRecord record : transports) {
+ if(record != null && TransportType.BLUETOOTH.equals(record.getType())) {
+ SdlDeviceListener.setSDLConnectedStatus(contextWeakReference.get(), record.getAddress(),true);
+ }
+ }
+ }
+
transportListener.onTransportConnected(transports);
return true;
}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java
new file mode 100644
index 000000000..a396d186e
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java
@@ -0,0 +1,278 @@
+/*
+ * Copyright (c) 2020 Livio, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * Neither the name of the Livio Inc. nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+package com.smartdevicelink.transport.utl;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.NonNull;
+
+import com.smartdevicelink.transport.MultiplexBaseTransport;
+import com.smartdevicelink.transport.MultiplexBluetoothTransport;
+import com.smartdevicelink.transport.SdlRouterService;
+import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.SdlAppInfo;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+
+
+public class SdlDeviceListener {
+
+ private static final String TAG = "SdlListener";
+ private static final int MIN_VERSION_REQUIRED = 13;
+ private static final String SDL_DEVICE_STATUS_SHARED_PREFS = "sdl.device.status";
+ private static final Object LOCK = new Object(), RUNNING_LOCK = new Object();
+
+ private final WeakReference<Context> contextWeakReference;
+ private final Callback callback;
+ private final BluetoothDevice connectedDevice;
+ private MultiplexBluetoothTransport bluetoothTransport;
+ private TransportHandler bluetoothHandler;
+ private Handler timeoutHandler;
+ private Runnable timeoutRunner;
+ private boolean isRunning = false;
+
+
+ public SdlDeviceListener(Context context, BluetoothDevice device, Callback callback) {
+ this.contextWeakReference = new WeakReference<>(context);
+ this.connectedDevice = device;
+ this.callback = callback;
+ }
+
+ /**
+ * This will start the SDL Device Listener with two paths. The first path will be a check
+ * against the supplied bluetooth device to see if it has already successfully connected as an
+ * SDL device. If it has, the supplied callback will be called immediately. If the device hasn't
+ * connected as an SDL device before, the SDL Device Listener will then open up an RFCOMM channel
+ * using the SDL UUID and await a potential connection. A timeout is used to ensure this only
+ * listens for a finite amount of time. If this is the first time the device has been seen, this
+ * will listen for 30 seconds, if it is not, this will listen for 15 seconds instead.
+ */
+ public void start() {
+ if(connectedDevice == null) {
+ DebugTool.logInfo(TAG + ": No supplied bluetooth device");
+ if(callback != null){
+ callback.onTransportError(null);
+ }
+ return;
+ }
+
+ if (hasSDLConnected(contextWeakReference.get(), connectedDevice.getAddress())) {
+ DebugTool.logInfo(TAG + ": Confirmed SDL device, should start router service");
+ //This device has connected to SDL previously, it is ok to start the RS right now
+ callback.onTransportConnected(contextWeakReference.get(), connectedDevice);
+ return;
+ }
+ synchronized (RUNNING_LOCK) {
+ isRunning = true;
+ // set timeout = if first time seeing BT device, 30s, if not 15s
+ int timeout = isFirstStatusCheck(connectedDevice.getAddress()) ? 30000 : 15000;
+ //Set our preference as false for this device for now
+ setSDLConnectedStatus(contextWeakReference.get(), connectedDevice.getAddress(), false);
+ bluetoothHandler = new TransportHandler(this);
+ bluetoothTransport = new MultiplexBluetoothTransport(bluetoothHandler);
+ bluetoothTransport.start();
+ timeoutRunner = new Runnable() {
+ @Override
+ public void run() {
+ if (bluetoothTransport != null) {
+ int state = bluetoothTransport.getState();
+ if (state != MultiplexBluetoothTransport.STATE_CONNECTED) {
+ DebugTool.logInfo(TAG + ": No bluetooth connection made");
+ bluetoothTransport.stop();
+ } //else BT is connected; it will close itself through callbacks
+ }
+ }
+ };
+ timeoutHandler = new Handler(Looper.getMainLooper());
+ timeoutHandler.postDelayed(timeoutRunner, timeout);
+ }
+ }
+
+ /**
+ * Check to see if this instance is in the middle of running or not
+ *
+ * @return if this is already in the process of running
+ */
+ public boolean isRunning() {
+ synchronized (RUNNING_LOCK) {
+ return isRunning;
+ }
+ }
+
+ private static class TransportHandler extends Handler {
+
+ final WeakReference<SdlDeviceListener> provider;
+
+ TransportHandler(SdlDeviceListener provider) {
+ this.provider = new WeakReference<>(provider);
+ }
+
+ @Override
+ public void handleMessage(@NonNull Message msg) {
+ if (this.provider.get() == null) {
+ return;
+ }
+ SdlDeviceListener sdlListener = this.provider.get();
+ switch (msg.what) {
+
+ case SdlRouterService.MESSAGE_STATE_CHANGE:
+ switch (msg.arg1) {
+ case MultiplexBaseTransport.STATE_CONNECTED:
+ sdlListener.setSDLConnectedStatus(sdlListener.contextWeakReference.get(), sdlListener.connectedDevice.getAddress(), true);
+ boolean keepConnectionOpen = sdlListener.callback.onTransportConnected(sdlListener.contextWeakReference.get(), sdlListener.connectedDevice);
+ if (!keepConnectionOpen) {
+ sdlListener.bluetoothTransport.stop();
+ sdlListener.bluetoothTransport = null;
+ sdlListener.timeoutHandler.removeCallbacks(sdlListener.timeoutRunner);
+ }
+ break;
+ case MultiplexBaseTransport.STATE_NONE:
+ // We've just lost the connection
+ sdlListener.callback.onTransportDisconnected(sdlListener.connectedDevice);
+ break;
+ case MultiplexBaseTransport.STATE_ERROR:
+ sdlListener.callback.onTransportError(sdlListener.connectedDevice);
+ break;
+ }
+ break;
+
+ case com.smartdevicelink.transport.SdlRouterService.MESSAGE_READ:
+ break;
+ }
+ }
+ }
+
+
+ /**
+ * Set the connection establishment status of the particular device
+ *
+ * @param address address of the device in question
+ * @param hasSDLConnected true if a connection has been established, false if not
+ */
+ public static void setSDLConnectedStatus(Context context, String address, boolean hasSDLConnected) {
+ synchronized (LOCK) {
+ if (context != null) {
+ DebugTool.logInfo(TAG + ": Saving connected status - " + address + " : " + hasSDLConnected);
+ SharedPreferences preferences = context.getSharedPreferences(SDL_DEVICE_STATUS_SHARED_PREFS, Context.MODE_PRIVATE);
+ if (preferences.contains(address) && hasSDLConnected == preferences.getBoolean(address, false)) {
+ //The same key/value exists in our shared preferences. No reason to write again.
+ return;
+ }
+ SharedPreferences.Editor editor = preferences.edit();
+ editor.putBoolean(address, hasSDLConnected);
+ editor.commit();
+ }
+ }
+ }
+
+ /**
+ * Checks to see if a device address has connected to SDL before.
+ *
+ * @param address the mac address of the device in question
+ * @return if this is the first status check of this device
+ */
+ private boolean isFirstStatusCheck(String address) {
+ synchronized (LOCK) {
+ Context context = contextWeakReference.get();
+ if (context != null) {
+ SharedPreferences preferences = context.getSharedPreferences(SDL_DEVICE_STATUS_SHARED_PREFS, Context.MODE_PRIVATE);
+ return !preferences.contains(address);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Checks to see if a device address has connected to SDL before.
+ *
+ * @param address the mac address of the device in question
+ * @return if an SDL connection has ever been established with this device
+ */
+ public static boolean hasSDLConnected(Context context, String address) {
+ synchronized (LOCK) {
+ if (context != null) {
+ SharedPreferences preferences = context.getSharedPreferences(SDL_DEVICE_STATUS_SHARED_PREFS, Context.MODE_PRIVATE);
+ return preferences.contains(address) && preferences.getBoolean(address, false);
+ }
+ return false;
+ }
+ }
+
+ /**
+ * This method will check the current device and list of SDL enabled apps to derive if the
+ * feature can be supported. Due to older libraries sending their intents to start the router
+ * service right at the bluetooth A2DP/HFS connections, this feature can't be used until all
+ * applications are updated to the point they include the feature.
+ *
+ * @param sdlAppInfoList current list of SDL enabled applications on the device
+ * @return if this feature is supported or not. If it is not, the caller should follow the
+ * previously used flow, ie start the router service.
+ */
+ public static boolean isFeatureSupported(List<SdlAppInfo> sdlAppInfoList) {
+
+ SdlAppInfo appInfo;
+ for (int i = sdlAppInfoList.size() - 1; i >= 0; i--) {
+ appInfo = sdlAppInfoList.get(i);
+ if (appInfo != null
+ && !appInfo.isCustomRouterService()
+ && appInfo.getRouterServiceVersion() < MIN_VERSION_REQUIRED) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Callback for the SdlDeviceListener. It will return if the supplied device makes a bluetooth
+ * connection on the SDL UUID RFCOMM chanel or not. Most of the time the only callback that
+ * matters will be the onTransportConnected.
+ */
+ public interface Callback {
+ /**
+ * @param bluetoothDevice the BT device that successfully connected to SDL's UUID
+ * @return if the RFCOMM connection should stay open. In most cases this should be false
+ */
+ boolean onTransportConnected(Context context, BluetoothDevice bluetoothDevice);
+
+ void onTransportDisconnected(BluetoothDevice bluetoothDevice);
+
+ void onTransportError(BluetoothDevice bluetoothDevice);
+ }
+}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java
new file mode 100644
index 000000000..bd31e746e
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/TransportRecord.java
@@ -0,0 +1,56 @@
+package com.smartdevicelink.transport.utl;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.smartdevicelink.transport.enums.TransportType;
+
+public class TransportRecord extends BaseTransportRecord implements Parcelable {
+
+ public TransportRecord(TransportType transportType, String address) {
+ super(transportType, address);
+ }
+
+ public TransportRecord(Parcel p) {
+ if (p.readInt() == 1) { //We should have a transport type attached
+ String transportName = p.readString();
+ if(transportName != null){
+ this.type = TransportType.valueOf(transportName);
+ }
+ }
+
+ if (p.readInt() == 1) { //We should have a transport address attached
+ address = p.readString();
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(type!=null? 1 : 0);
+ if(type != null){
+ dest.writeString(type.name());
+ }
+
+ dest.writeInt(address !=null? 1 : 0);
+ if(address != null){
+ dest.writeString(address);
+ }
+ }
+
+ public static final Parcelable.Creator<TransportRecord> CREATOR = new Parcelable.Creator<TransportRecord>() {
+ public TransportRecord createFromParcel(Parcel in) {
+ return new TransportRecord(in);
+ }
+
+ @Override
+ public TransportRecord[] newArray(int size) {
+ return new TransportRecord[size];
+ }
+
+ };
+}
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/HttpRequestTask.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/HttpRequestTask.java
new file mode 100644
index 000000000..3eee85a05
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/HttpRequestTask.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright (c) 2017 - 2019, SmartDeviceLink Consortium, 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 SmartDeviceLink Consortium, 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.util;
+
+import android.os.AsyncTask;
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.net.HttpURLConnection;
+import java.net.URL;
+
+public class HttpRequestTask extends AsyncTask<String, String, String> {
+ private static final String TAG = "Http Request Task";
+
+ public static final String REQUEST_TYPE_POST = "POST";
+ public static final String REQUEST_TYPE_GET = "GET";
+ public static final String REQUEST_TYPE_DELETE = "DELETE";
+
+ HttpRequestTaskCallback cb;
+
+ /**
+ * @param hcb callback for when this task finishes
+ * <br><br><b> - When calling execute, params as followed: </b><br>
+ * 1. Url String<br>
+ * 2. Request type (Defined in this class) REQUEST_TYPE_POST, REQUEST_TYPE_GET, REQUEST_TYPE_DELETE<br>
+ * 3. (Optional) Data to be sent. <br>
+ * 4. (Optional) Content Type Default will be application/json<br>
+ * 5. (Optional) Accept Type default will be application/json
+ *
+ */
+ public HttpRequestTask( HttpRequestTaskCallback hcb){
+ this.cb = hcb;
+ }
+
+ @Override
+ protected String doInBackground(String... params) {
+ int length = params.length;
+ String urlString = params[0];
+ String request_type = params[1];
+
+ //Grab and set data to be written if included
+ String data;
+ if(length>2){
+ data = params[2];
+ }else{
+ data = null;
+ }
+
+ //Grab and set content type for the header if included
+ String contentType;
+ if(length>3){
+ contentType = params[3];
+ }else{
+ contentType = "application/json";
+ }
+ //Grab and set accept type for the header if included
+ String acceptType;
+ if(length>4){
+ acceptType = params[4];
+ }else{
+ acceptType = "application/json";
+ }
+
+ if(urlString == null || request_type == null){
+ Log.e(TAG, "Can't process request, param error");
+ if(cb!=null){
+ cb.httpFailure(-1);
+ cb = null;
+ }
+ return "Error";
+ }
+
+ HttpURLConnection urlConnection = null;
+ BufferedReader reader = null;
+ try {
+ URL url = new URL(urlString);
+ urlConnection = (HttpURLConnection) url.openConnection();
+ urlConnection.setDoOutput(true);
+ urlConnection.setRequestMethod(request_type);
+ urlConnection.setRequestProperty("Content-Type", contentType);
+ urlConnection.setRequestProperty("Accept", acceptType);
+ //If we have data, we should write it out
+ if(data !=null){
+ Writer writer = new BufferedWriter(new OutputStreamWriter(urlConnection.getOutputStream(), "UTF-8"));
+ writer.write(data);
+ writer.close();
+ }
+ InputStream inputStream = urlConnection.getInputStream();
+
+ int responseCode = urlConnection.getResponseCode();
+ if (responseCode == 200) { //Success
+ //input stream
+ StringBuffer buffer = new StringBuffer();
+ if (inputStream == null) {
+ // Nothing to do.
+ if(cb!=null){
+ cb.httpCallComplete(null);
+ cb = null;
+ }
+ return null;
+ }
+ reader = new BufferedReader(new InputStreamReader(inputStream));
+
+ String inputLine;
+ while ((inputLine = reader.readLine()) != null)
+ buffer.append(inputLine).append("\n");
+ if (buffer.length() == 0) {
+ // Stream was empty. No point in parsing.
+ if(cb!=null){
+ cb.httpCallComplete(null);
+ cb = null;
+ }
+ return null;
+ }
+ String response = null;
+
+ response = buffer.toString();
+ //send to post execute
+ if(cb!=null){
+ cb.httpCallComplete(response);
+ cb = null;
+ }
+ return response;
+ }else{
+ if(cb!=null){
+ cb.httpFailure(responseCode);
+ cb = null;
+ }
+ Log.e(TAG, "Failed to download file - " + responseCode);
+ return null;
+ }
+
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (NullPointerException e){ // Only to catch error in urlConnection.getOutputStream() - when servers are down
+ e.printStackTrace();
+ urlConnection = null;
+ }
+ finally {
+ if (urlConnection != null) {
+ urlConnection.disconnect();
+ }
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (final IOException e) {
+ Log.e(TAG, "Error closing stream", e);
+ }
+ }
+ if(cb!=null){
+ cb.httpFailure(-1);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Callback interface for HTTP requests.
+ * @author Joey Grover
+ *
+ */
+ public interface HttpRequestTaskCallback{
+ /**
+ * Called when HTTP request is successfully completed.
+ * @param response The response to the HTTP request.
+ */
+ public abstract void httpCallComplete(String response);
+ /**
+ * Called when HTTP request failed.
+ * @param statusCode The HTTP failure code.
+ */
+ public abstract void httpFailure(int statusCode);
+ }
+
+}