summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoey Grover <joeygrover@gmail.com>2019-05-31 14:32:50 -0400
committerJoey Grover <joeygrover@gmail.com>2019-05-31 14:32:50 -0400
commit41be5c50c33dc7c537fbe04ca0cec2d265d0cde3 (patch)
tree6364c670eceab20adb11f52522776e4c1ad87ad0
parenta1c9407c775b61ae9a0d7f1d921ea379b71e7504 (diff)
downloadsdl_android-41be5c50c33dc7c537fbe04ca0cec2d265d0cde3.tar.gz
Audio requirement flag added.
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/SdlConnection/SdlSession2.java36
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java9
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTransportConfig.java23
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java268
4 files changed, 336 insertions, 0 deletions
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/SdlConnection/SdlSession2.java b/android/sdl_android/src/main/java/com/smartdevicelink/SdlConnection/SdlSession2.java
index c0294b1a3..aa864b817 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/SdlConnection/SdlSession2.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/SdlConnection/SdlSession2.java
@@ -32,6 +32,7 @@
package com.smartdevicelink.SdlConnection;
+import android.content.Context;
import android.util.Log;
import com.smartdevicelink.exception.SdlException;
@@ -45,8 +46,11 @@ import com.smartdevicelink.proxy.interfaces.ISdlServiceListener;
import com.smartdevicelink.transport.BaseTransportConfig;
import com.smartdevicelink.transport.MultiplexTransportConfig;
import com.smartdevicelink.transport.enums.TransportType;
+import com.smartdevicelink.util.DebugTool;
+import com.smartdevicelink.util.MediaStreamingStatus;
import com.smartdevicelink.util.Version;
+import java.lang.ref.WeakReference;
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -57,6 +61,9 @@ public class SdlSession2 extends SdlSession implements ISdlProtocol{
final protected SdlProtocol sdlProtocol;
+ WeakReference<Context> contextWeakReference;
+ MediaStreamingStatus mediaStreamingStatus;
+ boolean requiresAudioSupport = false;
@SuppressWarnings("SameReturnValue")
@Deprecated
@@ -66,11 +73,32 @@ public class SdlSession2 extends SdlSession implements ISdlProtocol{
public SdlSession2(ISdlConnectionListener listener, MultiplexTransportConfig config){
this.transportConfig = config;
+ if(config != null){
+ contextWeakReference = new WeakReference<>(config.getContext());
+ this.requiresAudioSupport = config.requiresAudioSupport();
+
+ }
this.sessionListener = listener;
this.sdlProtocol = new SdlProtocol(this,config);
}
+ boolean isAudioRequirementMet(){
+ if(mediaStreamingStatus == null){
+ mediaStreamingStatus = new MediaStreamingStatus(contextWeakReference.get(), new MediaStreamingStatus.Callback() {
+ @Override
+ public void onAudioNoLongerAvailable() {
+ close();
+ shutdown("Audio output no longer available");
+ }
+ });
+ }
+
+ // If requiresAudioSupport is false, or a supported audio output device is available
+ return !requiresAudioSupport || mediaStreamingStatus.isAudioOutputAvailable();
+
+ }
+
@Deprecated
@Override
public SdlConnection getSdlConnection() {
@@ -144,6 +172,11 @@ public class SdlSession2 extends SdlSession implements ISdlProtocol{
@SuppressWarnings("RedundantThrows")
@Override
public void startSession() throws SdlException {
+ if(!isAudioRequirementMet()){
+ shutdown("Audio output not available");
+ return;
+ }
+
sdlProtocol.start();
}
@@ -169,6 +202,9 @@ public class SdlSession2 extends SdlSession implements ISdlProtocol{
public void shutdown(String info){
Log.d(TAG, "Shutdown - " + info);
+ if(mediaStreamingStatus != null) {
+ mediaStreamingStatus.clear();
+ }
this.sessionListener.onTransportDisconnected(info);
}
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 ea1e88dcc..a46cfef18 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
@@ -1032,6 +1032,15 @@ public class SdlManager extends BaseSdlManager{
sdlManager.isMediaApp = false;
}
+ if(TransportType.MULTIPLEX.equals(sdlManager.transport.getTransportType())){
+ //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)sdlManager.transport).requiresAudioSupport() == null){
+ ((MultiplexTransportConfig)sdlManager.transport).setRequiresAudioSupport(sdlManager.isMediaApp);
+ }
+ }
+
+
if (sdlManager.lockScreenConfig == null){
// if lock screen params are not set, use default
sdlManager.lockScreenConfig = new LockScreenConfig();
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTransportConfig.java b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTransportConfig.java
index 65989307e..4d0ab0670 100644
--- a/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTransportConfig.java
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/transport/MultiplexTransportConfig.java
@@ -74,6 +74,7 @@ public class MultiplexTransportConfig extends BaseTransportConfig{
List<TransportType> primaryTransports, secondaryTransports;
boolean requiresHighBandwidth = false;
+ Boolean requiresAudioSupport = null;
TransportListener transportListener;
@@ -182,6 +183,28 @@ public class MultiplexTransportConfig extends BaseTransportConfig{
}
/**
+ * Set whether or not this app requires the use of an audio streaming output device
+ *
+ * @param requiresAudioSupport whether the app should be treated as requiring an audio streaming
+ * output device
+ */
+ public void setRequiresAudioSupport(boolean requiresAudioSupport){
+ this.requiresAudioSupport = requiresAudioSupport;
+ }
+
+ /**
+ * Get the setting from this config to see whether the app should be treated as requiring an
+ * audio streaming output device
+ *
+ * @return whether the app should be treated as requiring an audio streaming output device
+ */
+ public Boolean requiresAudioSupport(){
+ return this.requiresAudioSupport;
+ }
+
+
+
+ /**
* This will set the order in which a primary transport is determined to be accepted or not.
* In the case of previous protocol versions ( < 5.1)
* @param transports list of transports that can be used as primary
diff --git a/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java b/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java
new file mode 100644
index 000000000..36b5a6d6e
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java
@@ -0,0 +1,268 @@
+/*
+ * 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.util;
+
+import android.annotation.SuppressLint;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Possible improvements
+ *
+ * - Narrow down list of acceptable audio devices
+ * - Add ability to listen for when audio devices become available, and then connect
+ * - Improve redundant calls to create String arrays for action arrays
+ */
+
+public class MediaStreamingStatus {
+ private static final Object BROADCAST_RECEIVER_LOCK = new Object();
+
+
+ WeakReference<Context> contextWeakReference;
+ Callback callback;
+ List<String> intentList;
+
+ public MediaStreamingStatus(@NonNull Context context, @NonNull Callback callback){
+ contextWeakReference = new WeakReference<>(context);
+ this.callback = callback;
+ intentList = new ArrayList<>();
+ //This is a default action that should be added
+ intentList.add(AudioManager.ACTION_AUDIO_BECOMING_NOISY);
+ }
+
+ public void clear(){
+ callback = null;
+ unregisterBroadcastReceiver();
+ contextWeakReference.clear();
+
+ }
+
+ /*
+
+ Working order
+ ---------------------------------------------------------------------------------------------
+ 1. If API level >= 23, use AudioManager to get connected audio output devices.
+ Covers ~ 74.8% of Android devices as of 5/30/2019
+ This will return for a number of different supported audio devices. Full list can be seen
+ in the isSupportedAudioDevice method.
+
+ 2. If API level >= 3 && <=22, use the BluetoothManager to detect A2DP connection.
+ Covers ~ 25.2% of Android devices not covered in option 1 as of 5/30/2019
+ This will enforce that bluetooth is connected as an audio output. No other type of audio
+ device can currently be detected.
+
+ 3. If API level <= 2, return false.
+ Covers <1% of Android devices not covered by cases 1 and 2.
+
+ Other options considered included:
+ - BluetoothAdapter.getProfileConnectionState(BluetoothProfile.A2DP) == STATE_CONNECTED || STATE_CONNECTING ;
+ - MediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO).getDeviceType() == MediaRouter.RouteInfo.DEVICE_TYPE_BLUETOOTH ;
+
+ */
+
+ @SuppressLint("MissingPermission")
+ public synchronized boolean isAudioOutputAvailable() {
+ Context context = contextWeakReference.get();
+ if(context == null){ return false;}
+
+ AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+ // If API level 23+ audio manager can iterate over all current devices to see if a supported
+ // device is present.
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
+ AudioDeviceInfo[] deviceInfos = audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+ for(AudioDeviceInfo deviceInfo : deviceInfos){
+ if(deviceInfo != null && isSupportedAudioDevice(deviceInfo.getType())){
+ return true;
+ }
+ }
+ }
+
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE
+ && android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+
+ return audioManager.isBluetoothA2dpOn();
+ }
+
+ //If an acceptable audio device hasn't been found or the API level is too low, then only a
+ //value of false can be returned as there is not enough information to determine if an audio
+ //device is available.
+ return false;
+ }
+
+ @SuppressLint("MissingPermission")
+ private boolean isSupportedAudioDevice(int audioDevice){
+ DebugTool.logInfo("Audio device connected: " + audioDevice);
+ switch (audioDevice){
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if(adapter == null || !adapter.isEnabled() ){
+ //False positive
+ return false;
+ }
+ if(android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH ){
+ int state = adapter.getProfileConnectionState(BluetoothProfile.A2DP);
+ if(state != BluetoothAdapter.STATE_CONNECTING && state != BluetoothAdapter.STATE_CONNECTED){
+ //False positive
+ return false;
+ }
+ }
+ setupBluetoothBroadcastReceiver();
+ return true; //Make sure this doesn't fall to any other logic after this point
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_USB_ACCESSORY:
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ case AudioDeviceInfo.TYPE_USB_HEADSET:
+ setupUSBBroadcastReceiver();
+ return true;
+ case AudioDeviceInfo.TYPE_LINE_ANALOG:
+ case AudioDeviceInfo.TYPE_LINE_DIGITAL:
+ case AudioDeviceInfo.TYPE_WIRED_HEADSET:
+ case AudioDeviceInfo.TYPE_WIRED_HEADPHONES:
+ case AudioDeviceInfo.TYPE_AUX_LINE:
+ setupHeadsetBroadcastReceiver();
+ return true;
+ }
+ return false;
+ }
+
+
+ void setupBluetoothBroadcastReceiver(){
+ String[] actions = new String[4];
+ actions[0] = BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED;
+ actions[1] = BluetoothAdapter.ACTION_STATE_CHANGED;
+ actions[2] = BluetoothDevice.ACTION_ACL_DISCONNECTED;
+ actions[3] = BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED;
+
+ listenForIntents(actions);
+ }
+
+ void setupHeadsetBroadcastReceiver(){
+ String[] actions = new String[1];
+ actions[0] = AudioManager.ACTION_HEADSET_PLUG;
+
+ listenForIntents(actions);
+ }
+
+ void setupUSBBroadcastReceiver(){
+ String[] actions = new String[1];
+ actions[0] = Intent.ACTION_BATTERY_CHANGED;
+
+ listenForIntents(actions);
+ }
+
+ void listenForIntents(@NonNull String[] actions){
+ if(intentList != null){
+ //Add each intent
+ int preAddSize = intentList.size();
+
+ for(String action : actions){
+ if(action != null && action.length() > 0 && !intentList.contains(action)){
+ intentList.add(action);
+ }
+ }
+
+ if(preAddSize != intentList.size()){
+ updateBroadcastReceiver();
+ }
+ }
+ }
+
+ void updateBroadcastReceiver() {
+ //The broadcast receiver has not been setup for this yet
+ Context context = contextWeakReference.get();
+ if (context != null) {
+ IntentFilter intentFilter = new IntentFilter();
+ for (String intentAction : intentList) {
+ intentFilter.addAction(intentAction);
+ }
+ unregisterBroadcastReceiver();
+ //Re-register receiver
+ context.registerReceiver(broadcastReceiver, intentFilter);
+
+ }
+
+ }
+
+ void unregisterBroadcastReceiver(){
+ Context context = contextWeakReference.get();
+ if(context != null) {
+ try{
+ context.unregisterReceiver(broadcastReceiver);
+ }catch (Exception e){
+ //Ignore the exception
+ }
+ }
+ }
+
+ private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ boolean valid = true;
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (BROADCAST_RECEIVER_LOCK) {
+ if (!isAudioOutputAvailable()) {
+ if (valid) {
+ valid = false;
+ //No audio device is acceptable any longer
+ if (callback != null) {
+ callback.onAudioNoLongerAvailable();
+ }
+
+ unregisterBroadcastReceiver();
+ }
+ }
+ }
+ }
+ };
+
+ public interface Callback{
+ void onAudioNoLongerAvailable();
+ }
+
+
+
+}