Loading res/xml/sound_settings.xml +16 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,14 @@ android:order="-180" settings:controller="com.android.settings.notification.MediaVolumePreferenceController"/> <!-- Media output switcher --> <ListPreference android:key="media_output" android:title="@string/media_output_title" android:dialogTitle="@string/media_output_title" android:order="-175" settings:controller="com.android.settings.sound.MediaOutputPreferenceController"/> <!-- Ring volume --> <com.android.settings.notification.VolumeSeekBarPreference android:key="ring_volume" Loading @@ -45,6 +53,14 @@ android:title="@string/vibrate_when_ringing_title" android:order="-160"/> <!-- Hands free profile output switcher --> <ListPreference android:key="take_call_on_output" android:title="@string/take_call_on_title" android:dialogTitle="@string/take_call_on_title" android:order="-155" settings:controller="com.android.settings.sound.HandsFreeProfileOutputPreferenceController"/> <!-- Alarm volume --> <com.android.settings.notification.VolumeSeekBarPreference android:key="alarm_volume" Loading src/com/android/settings/core/FeatureFlags.java +1 −0 Original line number Diff line number Diff line Loading @@ -25,4 +25,5 @@ public class FeatureFlags { public static final String ABOUT_PHONE_V2 = "settings_about_phone_v2"; public static final String BLUETOOTH_WHILE_DRIVING = "settings_bluetooth_while_driving"; public static final String DATA_USAGE_SETTINGS_V2 = "settings_data_usage_v2"; public static final String AUDIO_SWITCHER_SETTINGS = "settings_audio_switcher"; } src/com/android/settings/sound/AudioSwitchPreferenceController.java 0 → 100644 +335 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.sound; import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.MediaRouter; import android.media.MediaRouter.Callback; import android.os.Handler; import android.os.Looper; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; import android.util.FeatureFlagUtils; import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settings.bluetooth.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.FeatureFlags; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; import java.util.List; /** * Abstract class for audio switcher controller to notify subclass * updating the current status of switcher entry. Subclasses must overwrite * {@link #setActiveBluetoothDevice(BluetoothDevice)} to set the * active device for corresponding profile. */ public abstract class AudioSwitchPreferenceController extends BasePreferenceController implements Preference.OnPreferenceChangeListener, BluetoothCallback, LifecycleObserver, OnStart, OnStop { private static final int INVALID_INDEX = -1; protected final AudioManager mAudioManager; protected final MediaRouter mMediaRouter; protected final LocalBluetoothProfileManager mProfileManager; protected int mSelectedIndex; protected Preference mPreference; protected List<BluetoothDevice> mConnectedDevices; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final LocalBluetoothManager mLocalBluetoothManager; private final MediaRouterCallback mMediaRouterCallback; private final WiredHeadsetBroadcastReceiver mReceiver; private final Handler mHandler; public AudioSwitchPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mLocalBluetoothManager = Utils.getLocalBtManager(mContext); mLocalBluetoothManager.setForegroundActivity(context); mProfileManager = mLocalBluetoothManager.getProfileManager(); mHandler = new Handler(Looper.getMainLooper()); mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); mReceiver = new WiredHeadsetBroadcastReceiver(); mMediaRouterCallback = new MediaRouterCallback(); } /** * Make this method as final, ensure that subclass will checking * the feature flag and they could mistakenly break it via overriding. */ @Override public final int getAvailabilityStatus() { return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS) ? AVAILABLE : DISABLED_UNSUPPORTED; } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { final String address = (String) newValue; if (!(preference instanceof ListPreference)) { return false; } final ListPreference listPreference = (ListPreference) preference; if (TextUtils.equals(address, mContext.getText(R.string.media_output_default_summary))) { // Switch to default device which address is device name mSelectedIndex = getDefaultDeviceIndex(); setActiveBluetoothDevice(null); listPreference.setSummary(mContext.getText(R.string.media_output_default_summary)); } else { // Switch to BT device which address is hardware address final int connectedDeviceIndex = getConnectedDeviceIndex(address); if (connectedDeviceIndex == INVALID_INDEX) { return false; } final BluetoothDevice btDevice = mConnectedDevices.get(connectedDeviceIndex); mSelectedIndex = connectedDeviceIndex; setActiveBluetoothDevice(btDevice); listPreference.setSummary(btDevice.getName()); } return true; } public abstract void setActiveBluetoothDevice(BluetoothDevice device); @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreference = screen.findPreference(mPreferenceKey); } @Override public void onStart() { register(); } @Override public void onStop() { unregister(); } /** * Only concerned about whether the local adapter is connected to any profile of any device and * are not really concerned about which profile. */ @Override public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { updateState(mPreference); } @Override public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { updateState(mPreference); } @Override public void onAudioModeChanged() { updateState(mPreference); } @Override public void onBluetoothStateChanged(int bluetoothState) { } /** * The local Bluetooth adapter has started the remote device discovery process. */ @Override public void onScanningStateChanged(boolean started) { } /** * Indicates a change in the bond state of a remote * device. For example, if a device is bonded (paired). */ @Override public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { updateState(mPreference); } @Override public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { } @Override public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { } protected boolean isOngoingCallStatus() { int audioMode = mAudioManager.getMode(); return audioMode == AudioManager.MODE_RINGTONE || audioMode == AudioManager.MODE_IN_CALL || audioMode == AudioManager.MODE_IN_COMMUNICATION; } int getDefaultDeviceIndex() { // Default device is after all connected devices. return ArrayUtils.size(mConnectedDevices); } void setupPreferenceEntries(CharSequence[] mediaOutputs, CharSequence[] mediaValues, BluetoothDevice activeDevice) { // default to current device mSelectedIndex = getDefaultDeviceIndex(); // default device is after all connected devices. mediaOutputs[mSelectedIndex] = mContext.getText(R.string.media_output_default_summary); // use default device name as address mediaValues[mSelectedIndex] = mContext.getText(R.string.media_output_default_summary); for (int i = 0, size = mConnectedDevices.size(); i < size; i++) { final BluetoothDevice btDevice = mConnectedDevices.get(i); mediaOutputs[i] = btDevice.getName(); mediaValues[i] = btDevice.getAddress(); if (btDevice.equals(activeDevice)) { // select the active connected device. mSelectedIndex = i; } } } void setPreference(CharSequence[] mediaOutputs, CharSequence[] mediaValues, Preference preference) { final ListPreference listPreference = (ListPreference) preference; listPreference.setEntries(mediaOutputs); listPreference.setEntryValues(mediaValues); listPreference.setValueIndex(mSelectedIndex); listPreference.setSummary(mediaOutputs[mSelectedIndex]); } private int getConnectedDeviceIndex(String hardwareAddress) { if (mConnectedDevices != null) { for (int i = 0, size = mConnectedDevices.size(); i < size; i++) { final BluetoothDevice btDevice = mConnectedDevices.get(i); if (TextUtils.equals(btDevice.getAddress(), hardwareAddress)) { return i; } } } return INVALID_INDEX; } private void register() { mLocalBluetoothManager.getEventManager().registerCallback(this); mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback); // Register for misc other intent broadcasts. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); intentFilter.addAction(STREAM_DEVICES_CHANGED_ACTION); mContext.registerReceiver(mReceiver, intentFilter); } private void unregister() { mLocalBluetoothManager.getEventManager().unregisterCallback(this); mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback); mMediaRouter.removeCallback(mMediaRouterCallback); mContext.unregisterReceiver(mReceiver); } /** Callback for headset plugged and unplugged events. */ private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { updateState(mPreference); } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] devices) { updateState(mPreference); } } /** Receiver for wired headset plugged and unplugged events. */ private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (AudioManager.ACTION_HEADSET_PLUG.equals(action) || AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) { updateState(mPreference); } } } /** Callback for cast device events. */ private class MediaRouterCallback extends Callback { @Override public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) { } @Override public void onRouteUnselected(MediaRouter router, int type, MediaRouter.RouteInfo info) { } @Override public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { if (info != null && !info.isDefault()) { // cast mode updateState(mPreference); } } @Override public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { } @Override public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { if (info != null && !info.isDefault()) { // cast mode updateState(mPreference); } } @Override public void onRouteGrouped(MediaRouter router, MediaRouter.RouteInfo info, MediaRouter.RouteGroup group, int index) { } @Override public void onRouteUngrouped(MediaRouter router, MediaRouter.RouteInfo info, MediaRouter.RouteGroup group) { } @Override public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo info) { } } } src/com/android/settings/sound/HandsFreeProfileOutputPreferenceController.java 0 → 100644 +94 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.sound; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.support.v7.preference.Preference; import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settingslib.bluetooth.HeadsetProfile; /** * This class allows switching between HFP-connected BT devices * while in on-call state. */ public class HandsFreeProfileOutputPreferenceController extends AudioSwitchPreferenceController { public HandsFreeProfileOutputPreferenceController(Context context, String key) { super(context, key); } @Override public void updateState(Preference preference) { if (preference == null) { // In case UI is not ready. return; } if (!isOngoingCallStatus()) { // Without phone call, disable the switch entry. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_default_summary)); return; } // Ongoing call status, list all the connected devices support hands free profile. // Select current active device. // Disable switch entry if there is no connected device. mConnectedDevices = null; BluetoothDevice activeDevice = null; final HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile(); if (headsetProfile != null) { mConnectedDevices = headsetProfile.getConnectedDevices(); activeDevice = headsetProfile.getActiveDevice(); } final int numDevices = ArrayUtils.size(mConnectedDevices); if (numDevices == 0) { // No connected devices, disable switch entry. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_default_summary)); return; } preference.setEnabled(true); CharSequence[] mediaOutputs = new CharSequence[numDevices + 1]; CharSequence[] mediaValues = new CharSequence[numDevices + 1]; // Setup devices entries, select active connected device setupPreferenceEntries(mediaOutputs, mediaValues, activeDevice); if (mAudioManager.isWiredHeadsetOn() && !mAudioManager.isBluetoothScoOn()) { // If wired headset is plugged in and active, select to default device. mSelectedIndex = getDefaultDeviceIndex(); } // Display connected devices, default device and show the active device setPreference(mediaOutputs, mediaValues, preference); } @Override public void setActiveBluetoothDevice(BluetoothDevice device) { if (isOngoingCallStatus()) { mProfileManager.getHeadsetProfile().setActiveDevice(device); } } } src/com/android/settings/sound/MediaOutputPreferenceController.java 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.sound; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.AudioManager; import android.media.MediaRouter; import android.support.v7.preference.Preference; import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settingslib.bluetooth.A2dpProfile; /** * This class which allows switching between a2dp-connected BT devices. * A few conditions will disable this switcher: * - No available BT device(s) * - Media stream captured by cast device * - During a call. */ public class MediaOutputPreferenceController extends AudioSwitchPreferenceController { public MediaOutputPreferenceController(Context context, String key) { super(context, key); } @Override public void updateState(Preference preference) { if (preference == null) { // In case UI is not ready. return; } if (mAudioManager.isMusicActiveRemotely() || isCastDevice(mMediaRouter)) { // TODO(76455906): Workaround for cast mode, need a solid way to identify cast mode. // In cast mode, disable switch entry. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_summary_unavailable)); return; } if (isOngoingCallStatus()) { // Ongoing call status, switch entry for media will be disabled. preference.setEnabled(false); preference.setSummary( mContext.getText(R.string.media_out_summary_ongoing_call_state)); return; } // Otherwise, list all of the A2DP connected device and display the active device. mConnectedDevices = null; BluetoothDevice activeDevice = null; if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) { final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); if (a2dpProfile != null) { mConnectedDevices = a2dpProfile.getConnectedDevices(); activeDevice = a2dpProfile.getActiveDevice(); } } final int numDevices = ArrayUtils.size(mConnectedDevices); if (numDevices == 0) { // Disable switch entry if there is no connected devices. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_default_summary)); return; } preference.setEnabled(true); CharSequence[] mediaOutputs = new CharSequence[numDevices + 1]; CharSequence[] mediaValues = new CharSequence[numDevices + 1]; // Setup devices entries, select active connected device setupPreferenceEntries(mediaOutputs, mediaValues, activeDevice); if (mAudioManager.isWiredHeadsetOn() && !mAudioManager.isBluetoothA2dpOn()) { // If wired headset is plugged in and active, select to default device. mSelectedIndex = getDefaultDeviceIndex(); } // Display connected devices, default device and show the active device setPreference(mediaOutputs, mediaValues, preference); } @Override public void setActiveBluetoothDevice(BluetoothDevice device) { if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) { mProfileManager.getA2dpProfile().setActiveDevice(device); } } private static boolean isCastDevice(MediaRouter mediaRouter) { final MediaRouter.RouteInfo selected = mediaRouter.getSelectedRoute( ROUTE_TYPE_REMOTE_DISPLAY); return selected != null && selected.getPresentationDisplay() != null && selected.getPresentationDisplay().isValid(); } } Loading
res/xml/sound_settings.xml +16 −0 Original line number Diff line number Diff line Loading @@ -30,6 +30,14 @@ android:order="-180" settings:controller="com.android.settings.notification.MediaVolumePreferenceController"/> <!-- Media output switcher --> <ListPreference android:key="media_output" android:title="@string/media_output_title" android:dialogTitle="@string/media_output_title" android:order="-175" settings:controller="com.android.settings.sound.MediaOutputPreferenceController"/> <!-- Ring volume --> <com.android.settings.notification.VolumeSeekBarPreference android:key="ring_volume" Loading @@ -45,6 +53,14 @@ android:title="@string/vibrate_when_ringing_title" android:order="-160"/> <!-- Hands free profile output switcher --> <ListPreference android:key="take_call_on_output" android:title="@string/take_call_on_title" android:dialogTitle="@string/take_call_on_title" android:order="-155" settings:controller="com.android.settings.sound.HandsFreeProfileOutputPreferenceController"/> <!-- Alarm volume --> <com.android.settings.notification.VolumeSeekBarPreference android:key="alarm_volume" Loading
src/com/android/settings/core/FeatureFlags.java +1 −0 Original line number Diff line number Diff line Loading @@ -25,4 +25,5 @@ public class FeatureFlags { public static final String ABOUT_PHONE_V2 = "settings_about_phone_v2"; public static final String BLUETOOTH_WHILE_DRIVING = "settings_bluetooth_while_driving"; public static final String DATA_USAGE_SETTINGS_V2 = "settings_data_usage_v2"; public static final String AUDIO_SWITCHER_SETTINGS = "settings_audio_switcher"; }
src/com/android/settings/sound/AudioSwitchPreferenceController.java 0 → 100644 +335 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.sound; import static android.media.AudioManager.STREAM_DEVICES_CHANGED_ACTION; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.bluetooth.BluetoothDevice; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.AudioDeviceCallback; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.media.MediaRouter; import android.media.MediaRouter.Callback; import android.os.Handler; import android.os.Looper; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; import android.text.TextUtils; import android.util.FeatureFlagUtils; import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settings.bluetooth.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.core.FeatureFlags; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; import java.util.List; /** * Abstract class for audio switcher controller to notify subclass * updating the current status of switcher entry. Subclasses must overwrite * {@link #setActiveBluetoothDevice(BluetoothDevice)} to set the * active device for corresponding profile. */ public abstract class AudioSwitchPreferenceController extends BasePreferenceController implements Preference.OnPreferenceChangeListener, BluetoothCallback, LifecycleObserver, OnStart, OnStop { private static final int INVALID_INDEX = -1; protected final AudioManager mAudioManager; protected final MediaRouter mMediaRouter; protected final LocalBluetoothProfileManager mProfileManager; protected int mSelectedIndex; protected Preference mPreference; protected List<BluetoothDevice> mConnectedDevices; private final AudioManagerAudioDeviceCallback mAudioManagerAudioDeviceCallback; private final LocalBluetoothManager mLocalBluetoothManager; private final MediaRouterCallback mMediaRouterCallback; private final WiredHeadsetBroadcastReceiver mReceiver; private final Handler mHandler; public AudioSwitchPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mMediaRouter = (MediaRouter) context.getSystemService(Context.MEDIA_ROUTER_SERVICE); mLocalBluetoothManager = Utils.getLocalBtManager(mContext); mLocalBluetoothManager.setForegroundActivity(context); mProfileManager = mLocalBluetoothManager.getProfileManager(); mHandler = new Handler(Looper.getMainLooper()); mAudioManagerAudioDeviceCallback = new AudioManagerAudioDeviceCallback(); mReceiver = new WiredHeadsetBroadcastReceiver(); mMediaRouterCallback = new MediaRouterCallback(); } /** * Make this method as final, ensure that subclass will checking * the feature flag and they could mistakenly break it via overriding. */ @Override public final int getAvailabilityStatus() { return FeatureFlagUtils.isEnabled(mContext, FeatureFlags.AUDIO_SWITCHER_SETTINGS) ? AVAILABLE : DISABLED_UNSUPPORTED; } @Override public boolean onPreferenceChange(Preference preference, Object newValue) { final String address = (String) newValue; if (!(preference instanceof ListPreference)) { return false; } final ListPreference listPreference = (ListPreference) preference; if (TextUtils.equals(address, mContext.getText(R.string.media_output_default_summary))) { // Switch to default device which address is device name mSelectedIndex = getDefaultDeviceIndex(); setActiveBluetoothDevice(null); listPreference.setSummary(mContext.getText(R.string.media_output_default_summary)); } else { // Switch to BT device which address is hardware address final int connectedDeviceIndex = getConnectedDeviceIndex(address); if (connectedDeviceIndex == INVALID_INDEX) { return false; } final BluetoothDevice btDevice = mConnectedDevices.get(connectedDeviceIndex); mSelectedIndex = connectedDeviceIndex; setActiveBluetoothDevice(btDevice); listPreference.setSummary(btDevice.getName()); } return true; } public abstract void setActiveBluetoothDevice(BluetoothDevice device); @Override public void displayPreference(PreferenceScreen screen) { super.displayPreference(screen); mPreference = screen.findPreference(mPreferenceKey); } @Override public void onStart() { register(); } @Override public void onStop() { unregister(); } /** * Only concerned about whether the local adapter is connected to any profile of any device and * are not really concerned about which profile. */ @Override public void onConnectionStateChanged(CachedBluetoothDevice cachedDevice, int state) { updateState(mPreference); } @Override public void onActiveDeviceChanged(CachedBluetoothDevice activeDevice, int bluetoothProfile) { updateState(mPreference); } @Override public void onAudioModeChanged() { updateState(mPreference); } @Override public void onBluetoothStateChanged(int bluetoothState) { } /** * The local Bluetooth adapter has started the remote device discovery process. */ @Override public void onScanningStateChanged(boolean started) { } /** * Indicates a change in the bond state of a remote * device. For example, if a device is bonded (paired). */ @Override public void onDeviceAdded(CachedBluetoothDevice cachedDevice) { updateState(mPreference); } @Override public void onDeviceDeleted(CachedBluetoothDevice cachedDevice) { } @Override public void onDeviceBondStateChanged(CachedBluetoothDevice cachedDevice, int bondState) { } protected boolean isOngoingCallStatus() { int audioMode = mAudioManager.getMode(); return audioMode == AudioManager.MODE_RINGTONE || audioMode == AudioManager.MODE_IN_CALL || audioMode == AudioManager.MODE_IN_COMMUNICATION; } int getDefaultDeviceIndex() { // Default device is after all connected devices. return ArrayUtils.size(mConnectedDevices); } void setupPreferenceEntries(CharSequence[] mediaOutputs, CharSequence[] mediaValues, BluetoothDevice activeDevice) { // default to current device mSelectedIndex = getDefaultDeviceIndex(); // default device is after all connected devices. mediaOutputs[mSelectedIndex] = mContext.getText(R.string.media_output_default_summary); // use default device name as address mediaValues[mSelectedIndex] = mContext.getText(R.string.media_output_default_summary); for (int i = 0, size = mConnectedDevices.size(); i < size; i++) { final BluetoothDevice btDevice = mConnectedDevices.get(i); mediaOutputs[i] = btDevice.getName(); mediaValues[i] = btDevice.getAddress(); if (btDevice.equals(activeDevice)) { // select the active connected device. mSelectedIndex = i; } } } void setPreference(CharSequence[] mediaOutputs, CharSequence[] mediaValues, Preference preference) { final ListPreference listPreference = (ListPreference) preference; listPreference.setEntries(mediaOutputs); listPreference.setEntryValues(mediaValues); listPreference.setValueIndex(mSelectedIndex); listPreference.setSummary(mediaOutputs[mSelectedIndex]); } private int getConnectedDeviceIndex(String hardwareAddress) { if (mConnectedDevices != null) { for (int i = 0, size = mConnectedDevices.size(); i < size; i++) { final BluetoothDevice btDevice = mConnectedDevices.get(i); if (TextUtils.equals(btDevice.getAddress(), hardwareAddress)) { return i; } } } return INVALID_INDEX; } private void register() { mLocalBluetoothManager.getEventManager().registerCallback(this); mAudioManager.registerAudioDeviceCallback(mAudioManagerAudioDeviceCallback, mHandler); mMediaRouter.addCallback(ROUTE_TYPE_REMOTE_DISPLAY, mMediaRouterCallback); // Register for misc other intent broadcasts. IntentFilter intentFilter = new IntentFilter(Intent.ACTION_HEADSET_PLUG); intentFilter.addAction(STREAM_DEVICES_CHANGED_ACTION); mContext.registerReceiver(mReceiver, intentFilter); } private void unregister() { mLocalBluetoothManager.getEventManager().unregisterCallback(this); mAudioManager.unregisterAudioDeviceCallback(mAudioManagerAudioDeviceCallback); mMediaRouter.removeCallback(mMediaRouterCallback); mContext.unregisterReceiver(mReceiver); } /** Callback for headset plugged and unplugged events. */ private class AudioManagerAudioDeviceCallback extends AudioDeviceCallback { @Override public void onAudioDevicesAdded(AudioDeviceInfo[] addedDevices) { updateState(mPreference); } @Override public void onAudioDevicesRemoved(AudioDeviceInfo[] devices) { updateState(mPreference); } } /** Receiver for wired headset plugged and unplugged events. */ private class WiredHeadsetBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); if (AudioManager.ACTION_HEADSET_PLUG.equals(action) || AudioManager.STREAM_DEVICES_CHANGED_ACTION.equals(action)) { updateState(mPreference); } } } /** Callback for cast device events. */ private class MediaRouterCallback extends Callback { @Override public void onRouteSelected(MediaRouter router, int type, MediaRouter.RouteInfo info) { } @Override public void onRouteUnselected(MediaRouter router, int type, MediaRouter.RouteInfo info) { } @Override public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) { if (info != null && !info.isDefault()) { // cast mode updateState(mPreference); } } @Override public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) { } @Override public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) { if (info != null && !info.isDefault()) { // cast mode updateState(mPreference); } } @Override public void onRouteGrouped(MediaRouter router, MediaRouter.RouteInfo info, MediaRouter.RouteGroup group, int index) { } @Override public void onRouteUngrouped(MediaRouter router, MediaRouter.RouteInfo info, MediaRouter.RouteGroup group) { } @Override public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo info) { } } }
src/com/android/settings/sound/HandsFreeProfileOutputPreferenceController.java 0 → 100644 +94 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.sound; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.support.v7.preference.Preference; import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settingslib.bluetooth.HeadsetProfile; /** * This class allows switching between HFP-connected BT devices * while in on-call state. */ public class HandsFreeProfileOutputPreferenceController extends AudioSwitchPreferenceController { public HandsFreeProfileOutputPreferenceController(Context context, String key) { super(context, key); } @Override public void updateState(Preference preference) { if (preference == null) { // In case UI is not ready. return; } if (!isOngoingCallStatus()) { // Without phone call, disable the switch entry. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_default_summary)); return; } // Ongoing call status, list all the connected devices support hands free profile. // Select current active device. // Disable switch entry if there is no connected device. mConnectedDevices = null; BluetoothDevice activeDevice = null; final HeadsetProfile headsetProfile = mProfileManager.getHeadsetProfile(); if (headsetProfile != null) { mConnectedDevices = headsetProfile.getConnectedDevices(); activeDevice = headsetProfile.getActiveDevice(); } final int numDevices = ArrayUtils.size(mConnectedDevices); if (numDevices == 0) { // No connected devices, disable switch entry. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_default_summary)); return; } preference.setEnabled(true); CharSequence[] mediaOutputs = new CharSequence[numDevices + 1]; CharSequence[] mediaValues = new CharSequence[numDevices + 1]; // Setup devices entries, select active connected device setupPreferenceEntries(mediaOutputs, mediaValues, activeDevice); if (mAudioManager.isWiredHeadsetOn() && !mAudioManager.isBluetoothScoOn()) { // If wired headset is plugged in and active, select to default device. mSelectedIndex = getDefaultDeviceIndex(); } // Display connected devices, default device and show the active device setPreference(mediaOutputs, mediaValues, preference); } @Override public void setActiveBluetoothDevice(BluetoothDevice device) { if (isOngoingCallStatus()) { mProfileManager.getHeadsetProfile().setActiveDevice(device); } } }
src/com/android/settings/sound/MediaOutputPreferenceController.java 0 → 100644 +116 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.sound; import static android.media.MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY; import android.bluetooth.BluetoothDevice; import android.content.Context; import android.media.AudioManager; import android.media.MediaRouter; import android.support.v7.preference.Preference; import com.android.internal.util.ArrayUtils; import com.android.settings.R; import com.android.settingslib.bluetooth.A2dpProfile; /** * This class which allows switching between a2dp-connected BT devices. * A few conditions will disable this switcher: * - No available BT device(s) * - Media stream captured by cast device * - During a call. */ public class MediaOutputPreferenceController extends AudioSwitchPreferenceController { public MediaOutputPreferenceController(Context context, String key) { super(context, key); } @Override public void updateState(Preference preference) { if (preference == null) { // In case UI is not ready. return; } if (mAudioManager.isMusicActiveRemotely() || isCastDevice(mMediaRouter)) { // TODO(76455906): Workaround for cast mode, need a solid way to identify cast mode. // In cast mode, disable switch entry. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_summary_unavailable)); return; } if (isOngoingCallStatus()) { // Ongoing call status, switch entry for media will be disabled. preference.setEnabled(false); preference.setSummary( mContext.getText(R.string.media_out_summary_ongoing_call_state)); return; } // Otherwise, list all of the A2DP connected device and display the active device. mConnectedDevices = null; BluetoothDevice activeDevice = null; if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) { final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); if (a2dpProfile != null) { mConnectedDevices = a2dpProfile.getConnectedDevices(); activeDevice = a2dpProfile.getActiveDevice(); } } final int numDevices = ArrayUtils.size(mConnectedDevices); if (numDevices == 0) { // Disable switch entry if there is no connected devices. preference.setEnabled(false); preference.setSummary(mContext.getText(R.string.media_output_default_summary)); return; } preference.setEnabled(true); CharSequence[] mediaOutputs = new CharSequence[numDevices + 1]; CharSequence[] mediaValues = new CharSequence[numDevices + 1]; // Setup devices entries, select active connected device setupPreferenceEntries(mediaOutputs, mediaValues, activeDevice); if (mAudioManager.isWiredHeadsetOn() && !mAudioManager.isBluetoothA2dpOn()) { // If wired headset is plugged in and active, select to default device. mSelectedIndex = getDefaultDeviceIndex(); } // Display connected devices, default device and show the active device setPreference(mediaOutputs, mediaValues, preference); } @Override public void setActiveBluetoothDevice(BluetoothDevice device) { if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) { mProfileManager.getA2dpProfile().setActiveDevice(device); } } private static boolean isCastDevice(MediaRouter mediaRouter) { final MediaRouter.RouteInfo selected = mediaRouter.getSelectedRoute( ROUTE_TYPE_REMOTE_DISPLAY); return selected != null && selected.getPresentationDisplay() != null && selected.getPresentationDisplay().isValid(); } }