diff options
author | Brett <3911458+BrettyWhite@users.noreply.github.com> | 2019-06-17 12:56:38 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-06-17 12:56:38 -0400 |
commit | 22bb0606a68c4dc7038301d8c72d0a462becb2dc (patch) | |
tree | dfc3494b236ef3e28a14c0e955058c280efba6a1 | |
parent | 646f8ae83b70a1ab2feca961f37191d8cab4aa66 (diff) | |
parent | f860992b1a13a07aa92f1ff0da22429f7dcedb41 (diff) | |
download | sdl_android-22bb0606a68c4dc7038301d8c72d0a462becb2dc.tar.gz |
Merge pull request #1085 from smartdevicelink/feature/audio_requirement
Add audio requirement ability
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(); + } + +} |