summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrett <3911458+BrettyWhite@users.noreply.github.com>2019-06-17 12:56:38 -0400
committerGitHub <noreply@github.com>2019-06-17 12:56:38 -0400
commit22bb0606a68c4dc7038301d8c72d0a462becb2dc (patch)
treedfc3494b236ef3e28a14c0e955058c280efba6a1
parent646f8ae83b70a1ab2feca961f37191d8cab4aa66 (diff)
parentf860992b1a13a07aa92f1ff0da22429f7dcedb41 (diff)
downloadsdl_android-22bb0606a68c4dc7038301d8c72d0a462becb2dc.tar.gz
Merge pull request #1085 from smartdevicelink/feature/audio_requirement
Add audio requirement ability
-rw-r--r--android/sdl_android/src/androidTest/java/com/smartdevicelink/util/MediaStreamingStatusTests.java170
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/SdlConnection/SdlSession2.java35
-rw-r--r--android/sdl_android/src/main/java/com/smartdevicelink/managers/SdlManager.java27
-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.java300
5 files changed, 554 insertions, 1 deletions
diff --git a/android/sdl_android/src/androidTest/java/com/smartdevicelink/util/MediaStreamingStatusTests.java b/android/sdl_android/src/androidTest/java/com/smartdevicelink/util/MediaStreamingStatusTests.java
new file mode 100644
index 000000000..471bc19f0
--- /dev/null
+++ b/android/sdl_android/src/androidTest/java/com/smartdevicelink/util/MediaStreamingStatusTests.java
@@ -0,0 +1,170 @@
+package com.smartdevicelink.util;
+
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.os.Build;
+
+import com.smartdevicelink.AndroidTestCase2;
+import com.smartdevicelink.managers.SdlManager;
+import com.smartdevicelink.managers.SdlManagerListener;
+import com.smartdevicelink.proxy.rpc.enums.AppHMIType;
+import com.smartdevicelink.test.Test;
+import com.smartdevicelink.transport.MultiplexTransportConfig;
+
+import org.mockito.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.Vector;
+
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+
+public class MediaStreamingStatusTests extends AndroidTestCase2 {
+
+
+
+ @Mock
+ private AudioManager audioManager = mock(AudioManager.class);
+
+ @Mock
+ Context mockedContext;
+
+ MediaStreamingStatus defaultMediaStreamingStatus;
+
+ private Answer<Object> onGetSystemService = new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ String serviceName = (String) args[0];
+ if(serviceName != null && serviceName.equalsIgnoreCase(Context.AUDIO_SERVICE)){
+ return audioManager;
+ }else{
+ return null;
+ }
+ }
+ };
+
+
+ @Override
+ public void setUp() throws Exception{
+ mockedContext = mock(Context.class);
+ doAnswer(onGetSystemService).when(mockedContext).getSystemService(Context.AUDIO_SERVICE);
+ defaultMediaStreamingStatus = new MediaStreamingStatus(mockedContext, mock(MediaStreamingStatus.Callback.class));
+ }
+
+
+ public void testEmptyAudioDeviceInfoList(){
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertNotNull(mockedContext);
+ MediaStreamingStatus mediaStreamingStatus = new MediaStreamingStatus(mockedContext, new MediaStreamingStatus.Callback() {
+ @Override
+ public void onAudioNoLongerAvailable() {
+
+ }
+ });
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return new AudioDeviceInfo[0];
+ }
+ }).when(audioManager).getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+
+
+ assertFalse(mediaStreamingStatus.isAudioOutputAvailable());
+ }
+ }
+
+ public void testNullAudioDeviceInfoList(){
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ assertNotNull(mockedContext);
+ MediaStreamingStatus mediaStreamingStatus = new MediaStreamingStatus(mockedContext, mock(MediaStreamingStatus.Callback.class));
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return null;
+ }
+ }).when(audioManager).getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+
+ assertFalse(mediaStreamingStatus.isAudioOutputAvailable());
+ }
+ }
+
+
+ public void testSdlManagerMedia(){
+ SdlManager.Builder builder = new SdlManager.Builder(getContext(), Test.GENERAL_FULL_APP_ID, Test.GENERAL_STRING, mock(SdlManagerListener.class));
+ Vector<AppHMIType> appType = new Vector<>();
+ appType.add(AppHMIType.MEDIA);
+ builder.setAppTypes(appType);
+ MultiplexTransportConfig multiplexTransportConfig = new MultiplexTransportConfig(getContext(),Test.GENERAL_FULL_APP_ID);
+
+ assertNull(multiplexTransportConfig.requiresAudioSupport());
+ builder.setTransportType(multiplexTransportConfig);
+
+ SdlManager manager = builder.build();
+ manager.start();
+
+ //Original reference should be updated
+ assertTrue(multiplexTransportConfig.requiresAudioSupport());
+ }
+
+ public void testSdlManagerNonMedia(){
+ SdlManager.Builder builder = new SdlManager.Builder(getContext(), Test.GENERAL_FULL_APP_ID, Test.GENERAL_STRING, mock(SdlManagerListener.class));
+ Vector<AppHMIType> appType = new Vector<>();
+ appType.add(AppHMIType.DEFAULT);
+ builder.setAppTypes(appType);
+ MultiplexTransportConfig multiplexTransportConfig = new MultiplexTransportConfig(getContext(),Test.GENERAL_FULL_APP_ID);
+
+ assertNull(multiplexTransportConfig.requiresAudioSupport());
+ builder.setTransportType(multiplexTransportConfig);
+
+ SdlManager manager = builder.build();
+ manager.start();
+
+ //Original reference should be updated
+ assertFalse(multiplexTransportConfig.requiresAudioSupport());
+ }
+
+ public void testAcceptedBTDevices(){
+ MediaStreamingStatus mediaStreamingStatus = spy(new MediaStreamingStatus(getContext(), mock(MediaStreamingStatus.Callback.class)));
+
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return true;
+ }
+ }).when(mediaStreamingStatus).isBluetoothActuallyAvailable();
+
+ assertTrue(mediaStreamingStatus.isBluetoothActuallyAvailable());
+ assertTrue(mediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP));
+ }
+
+ public void testAcceptedUSBDevices(){
+ MediaStreamingStatus mediaStreamingStatus = spy(new MediaStreamingStatus(getContext(), mock(MediaStreamingStatus.Callback.class)));
+
+ doAnswer(new Answer() {
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ return true;
+ }
+ }).when(mediaStreamingStatus).isUsbActuallyConnected();
+
+ assertTrue(mediaStreamingStatus.isUsbActuallyConnected());
+ assertTrue(mediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_USB_DEVICE));
+ assertTrue(mediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_USB_ACCESSORY));
+ assertTrue(mediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_USB_HEADSET));
+ assertTrue(mediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_DOCK));
+ }
+
+ public void testAcceptedLineDevices(){
+ assertTrue(defaultMediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_LINE_ANALOG));
+ assertTrue(defaultMediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_LINE_DIGITAL));
+ assertTrue(defaultMediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_AUX_LINE));
+ assertTrue(defaultMediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_WIRED_HEADSET));
+ assertTrue(defaultMediaStreamingStatus.isSupportedAudioDevice(AudioDeviceInfo.TYPE_WIRED_HEADPHONES));
+ }
+
+
+}
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..457bd9a46 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,10 @@ 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.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 +60,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 +72,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 = Boolean.TRUE.equals(config.requiresAudioSupport()); //handle null case
+
+ }
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 +171,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 +201,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..9badd0619 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
@@ -577,6 +577,12 @@ public class SdlManager extends BaseSdlManager{
}
}
});
+
+ //If the requires audio support has not been set, it should be set to true if the
+ //app is a media app, and false otherwise
+ if(multiplexTransportConfig.requiresAudioSupport() == null){
+ multiplexTransportConfig.setRequiresAudioSupport(isMediaApp);
+ }
}
proxy = new SdlProxyBase(proxyBridge, context, appName, shortAppName, isMediaApp, hmiLanguage,
@@ -826,6 +832,21 @@ public class SdlManager extends BaseSdlManager{
setAppName(appName);
setManagerListener(listener);
}
+ /**
+ * Builder for the SdlManager. Parameters in the constructor are required.
+ * @param context the current context
+ * @param appId the app's ID
+ * @param appName the app's name
+ * @param listener a SdlManagerListener object
+ */
+ public Builder(@NonNull Context context, @NonNull final String appId, @NonNull final String appName, @NonNull BaseTransportConfig transport, @NonNull final SdlManagerListener listener){
+ sdlManager = new SdlManager();
+ setContext(context);
+ setAppId(appId);
+ setAppName(appName);
+ setTransportType(transport);
+ setManagerListener(listener);
+ }
/**
* Sets the App ID
@@ -969,7 +990,7 @@ public class SdlManager extends BaseSdlManager{
* Sets the BaseTransportConfig
* @param transport the type of transport that should be used for this SdlManager instance.
*/
- public Builder setTransportType(BaseTransportConfig transport){
+ public Builder setTransportType(@NonNull BaseTransportConfig transport){
sdlManager.transport = transport;
return this;
}
@@ -1025,6 +1046,10 @@ public class SdlManager extends BaseSdlManager{
throw new IllegalArgumentException("You must set a SdlManagerListener object");
}
+ if (sdlManager.transport == null) {
+ throw new IllegalArgumentException("You must set a transport type object");
+ }
+
if (sdlManager.hmiTypes == null) {
Vector<AppHMIType> hmiTypesDefault = new Vector<>();
hmiTypesDefault.add(AppHMIType.DEFAULT);
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..143b6c646
--- /dev/null
+++ b/android/sdl_android/src/main/java/com/smartdevicelink/util/MediaStreamingStatus.java
@@ -0,0 +1,300 @@
+/*
+ * 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();
+
+ private boolean broadcastReceiverValid = true;
+ private WeakReference<Context> contextWeakReference;
+ private Callback callback;
+ private 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);
+ if(deviceInfos != null) {
+ for (AudioDeviceInfo deviceInfo : deviceInfos) {
+ if (deviceInfo != null && isSupportedAudioDevice(deviceInfo.getType())) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ 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;
+ }
+
+ /**
+ * This method will check to ensure that the device is supported. If possible, it will also
+ * check against known variables and flags to ensure that that device is connected. This is
+ * required as the AudioManager tends to be untrustworthy.
+ * @param audioDevice
+ * @return
+ */
+ boolean isSupportedAudioDevice(int audioDevice){
+ DebugTool.logInfo("Audio device connected: " + audioDevice);
+ switch (audioDevice){
+ case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP:
+ if(isBluetoothActuallyAvailable()) {
+ setupBluetoothBroadcastReceiver();
+ return true; //Make sure this doesn't fall to any other logic after this point
+ }
+ return false;
+ case AudioDeviceInfo.TYPE_DOCK:
+ case AudioDeviceInfo.TYPE_USB_ACCESSORY:
+ case AudioDeviceInfo.TYPE_USB_DEVICE:
+ case AudioDeviceInfo.TYPE_USB_HEADSET:
+ if(isUsbActuallyConnected()) {
+ setupUSBBroadcastReceiver();
+ return true;
+ }
+ return false;
+ 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;
+ }
+
+ @SuppressLint("MissingPermission")
+ boolean isBluetoothActuallyAvailable(){
+ 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;
+ }
+ }
+
+ return true;
+ }
+
+ boolean isUsbActuallyConnected(){
+ Context context = contextWeakReference.get();
+ if(context != null){
+ return AndroidTools.isUSBCableConnected(context);
+ }
+ //default to true
+ return true;
+ }
+
+
+ private 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);
+ }
+
+ private void setupHeadsetBroadcastReceiver(){
+ String[] actions = new String[1];
+ actions[0] = AudioManager.ACTION_HEADSET_PLUG;
+
+ listenForIntents(actions);
+ }
+
+ private void setupUSBBroadcastReceiver(){
+ String[] actions = new String[1];
+ actions[0] = Intent.ACTION_BATTERY_CHANGED;
+
+ listenForIntents(actions);
+ }
+
+ private 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()){
+ synchronized (BROADCAST_RECEIVER_LOCK){
+ broadcastReceiverValid = true;
+ }
+ updateBroadcastReceiver();
+ }
+ }
+ }
+
+ private 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);
+
+ }
+
+ }
+
+ private void unregisterBroadcastReceiver(){
+ Context context = contextWeakReference.get();
+ if(context != null) {
+ try{
+ context.unregisterReceiver(broadcastReceiver);
+ }catch (Exception e){
+ //Ignore the exception
+ }
+ }
+ }
+
+ private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ synchronized (BROADCAST_RECEIVER_LOCK) {
+ if (!isAudioOutputAvailable()) {
+ if (broadcastReceiverValid) {
+ broadcastReceiverValid = false;
+ //No audio device is acceptable any longer
+ if (callback != null) {
+ callback.onAudioNoLongerAvailable();
+ }
+
+ intentList.clear();
+ unregisterBroadcastReceiver();
+ }
+ }
+ }
+ }
+ };
+
+
+ public interface Callback{
+ void onAudioNoLongerAvailable();
+ }
+
+}