summaryrefslogtreecommitdiff
path: root/android/sdl_android
diff options
context:
space:
mode:
authorJoey Grover <joeygrover@gmail.com>2020-06-12 11:04:57 -0400
committerJoey Grover <joeygrover@gmail.com>2020-06-12 11:04:57 -0400
commit1aa070be0804493907d2852a98b887b5c0b65398 (patch)
tree948dc954ff6809b5ee31b89ffff5ef0884cf8dbb /android/sdl_android
parentdfcdd324cdb9a2a84e237726ca6032d96f4cced0 (diff)
downloadsdl_android-1aa070be0804493907d2852a98b887b5c0b65398.tar.gz
Add SdlDeviceListener
Diffstat (limited to 'android/sdl_android')
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java263
1 files changed, 263 insertions, 0 deletions
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..c6d458052
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/utl/SdlDeviceListener.java
@@ -0,0 +1,263 @@
+/*
+ * 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(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 quesiton
+ * @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 quesiton
+ * @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 quesiton
+ * @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);
+ }
+}