1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.sound; 18 19 import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting; 20 21 import android.bluetooth.BluetoothDevice; 22 import android.bluetooth.BluetoothLeBroadcast; 23 import android.bluetooth.BluetoothLeBroadcastMetadata; 24 import android.content.Context; 25 import android.content.Intent; 26 import android.media.AudioManager; 27 import android.media.session.MediaController; 28 import android.media.session.MediaSessionManager; 29 import android.text.TextUtils; 30 31 import androidx.annotation.NonNull; 32 import androidx.annotation.Nullable; 33 import androidx.preference.Preference; 34 import androidx.preference.PreferenceScreen; 35 36 import com.android.settings.R; 37 import com.android.settings.media.MediaOutputUtils; 38 import com.android.settingslib.Utils; 39 import com.android.settingslib.bluetooth.A2dpProfile; 40 import com.android.settingslib.bluetooth.HearingAidProfile; 41 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast; 42 import com.android.settingslib.bluetooth.LocalBluetoothManager; 43 import com.android.settingslib.media.MediaOutputConstants; 44 import com.android.settingslib.media.PhoneMediaDevice; 45 46 import java.util.List; 47 48 /** 49 * This class allows launching MediaOutputDialog to switch output device. 50 * Preference would hide only when 51 * - Bluetooth = OFF 52 * - Bluetooth = ON and Connected Devices = 0 and Previously Connected = 0 53 * - Media stream captured by remote device 54 * - During a call. 55 */ 56 public class MediaOutputPreferenceController extends AudioSwitchPreferenceController { 57 58 private static final String TAG = "MediaOutputPreferenceController"; 59 @Nullable private MediaController mMediaController; 60 private MediaSessionManager mMediaSessionManager; 61 62 @Nullable private LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast; 63 64 private final BluetoothLeBroadcast.Callback mBroadcastCallback = 65 new BluetoothLeBroadcast.Callback() { 66 @Override 67 public void onBroadcastStarted(int reason, int broadcastId) { 68 updateState(mPreference); 69 } 70 71 @Override 72 public void onBroadcastStartFailed(int reason) { 73 updateState(mPreference); 74 } 75 76 @Override 77 public void onBroadcastStopped(int reason, int broadcastId) { 78 updateState(mPreference); 79 } 80 81 @Override 82 public void onBroadcastStopFailed(int reason) { 83 updateState(mPreference); 84 } 85 86 @Override 87 public void onPlaybackStarted(int reason, int broadcastId) {} 88 89 @Override 90 public void onPlaybackStopped(int reason, int broadcastId) {} 91 92 @Override 93 public void onBroadcastUpdated(int reason, int broadcastId) {} 94 95 @Override 96 public void onBroadcastUpdateFailed(int reason, int broadcastId) {} 97 98 @Override 99 public void onBroadcastMetadataChanged( 100 int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {} 101 }; 102 MediaOutputPreferenceController(Context context, String key)103 public MediaOutputPreferenceController(Context context, String key) { 104 super(context, key); 105 mMediaSessionManager = context.getSystemService(MediaSessionManager.class); 106 mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager); 107 LocalBluetoothManager localBluetoothManager = 108 com.android.settings.bluetooth.Utils.getLocalBtManager(mContext); 109 if (localBluetoothManager != null) { 110 mLocalBluetoothLeBroadcast = 111 localBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 112 } 113 } 114 115 @Override onStart()116 public void onStart() { 117 super.onStart(); 118 if (mLocalBluetoothLeBroadcast != null) { 119 mLocalBluetoothLeBroadcast.registerServiceCallBack( 120 mContext.getMainExecutor(), mBroadcastCallback); 121 } 122 } 123 124 @Override onStop()125 public void onStop() { 126 super.onStop(); 127 if (mLocalBluetoothLeBroadcast != null) { 128 mLocalBluetoothLeBroadcast.unregisterServiceCallBack(mBroadcastCallback); 129 } 130 } 131 132 @Override displayPreference(PreferenceScreen screen)133 public void displayPreference(PreferenceScreen screen) { 134 super.displayPreference(screen); 135 136 // Always use media switcher to control routing in desktop. 137 if (PhoneMediaDevice.inputRoutingEnabledAndIsDesktop(mContext)) { 138 mPreference.setVisible(true); 139 return; 140 } 141 142 mPreference.setVisible(!Utils.isAudioModeOngoingCall(mContext) 143 && (enableOutputSwitcherForSystemRouting() ? true : mMediaController != null)); 144 } 145 146 @Override updateState(Preference preference)147 public void updateState(Preference preference) { 148 if (preference == null) { 149 // In case UI is not ready. 150 return; 151 } 152 153 if (enableOutputSwitcherForSystemRouting()) { 154 mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager); 155 } else { 156 if (mMediaController == null) { 157 // No active local playback 158 return; 159 } 160 } 161 162 mPreference.setEnabled(true); 163 if (Utils.isAudioModeOngoingCall(mContext) && 164 !PhoneMediaDevice.inputRoutingEnabledAndIsDesktop(mContext)) { 165 // Ongoing call status, switch entry for media will be disabled, unless input routing is 166 // enabled in desktop. 167 mPreference.setVisible(false); 168 preference.setSummary( 169 mContext.getText(R.string.media_out_summary_ongoing_call_state)); 170 return; 171 } 172 173 BluetoothDevice activeDevice = null; 174 // Show preference if there is connected or previously connected device 175 // Find active device and set its name as the preference's summary 176 List<BluetoothDevice> connectedA2dpDevices = getConnectedA2dpDevices(); 177 List<BluetoothDevice> connectedHADevices = getConnectedHearingAidDevices(); 178 List<BluetoothDevice> connectedLeAudioDevices = getConnectedLeAudioDevices(); 179 if (mAudioManager.getMode() == AudioManager.MODE_NORMAL 180 && ((connectedA2dpDevices != null && !connectedA2dpDevices.isEmpty()) 181 || (connectedHADevices != null && !connectedHADevices.isEmpty()) 182 || (connectedLeAudioDevices != null && !connectedLeAudioDevices.isEmpty()))) { 183 activeDevice = findActiveDevice(); 184 } 185 186 if (mMediaController == null) { 187 mPreference.setTitle(mContext.getString(R.string.media_output_title_without_playing)); 188 } else { 189 mPreference.setTitle(mContext.getString(R.string.media_output_label_title, 190 com.android.settings.Utils.getApplicationLabel(mContext, 191 mMediaController.getPackageName()))); 192 } 193 if (isDeviceBroadcasting()) { 194 mPreference.setSummary(R.string.media_output_audio_sharing); 195 mPreference.setEnabled(false); 196 } else { 197 mPreference.setSummary( 198 (activeDevice == null) 199 ? mContext.getText(R.string.media_output_default_summary) 200 : activeDevice.getAlias()); 201 } 202 } 203 204 @Override findActiveDevice()205 public BluetoothDevice findActiveDevice() { 206 BluetoothDevice haActiveDevice = findActiveHearingAidDevice(); 207 BluetoothDevice leAudioActiveDevice = findActiveLeAudioDevice(); 208 final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); 209 210 if (haActiveDevice != null) { 211 return haActiveDevice; 212 } 213 214 if (leAudioActiveDevice != null) { 215 return leAudioActiveDevice; 216 } 217 218 if (a2dpProfile != null && a2dpProfile.getActiveDevice() != null) { 219 return a2dpProfile.getActiveDevice(); 220 } 221 222 return null; 223 } 224 225 /** 226 * Find active hearing aid device 227 */ 228 @Override findActiveHearingAidDevice()229 protected BluetoothDevice findActiveHearingAidDevice() { 230 final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); 231 232 if (hearingAidProfile != null) { 233 List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices(); 234 for (BluetoothDevice btDevice : activeDevices) { 235 if (btDevice != null) { 236 return btDevice; 237 } 238 } 239 } 240 return null; 241 } 242 243 @Override handlePreferenceTreeClick(Preference preference)244 public boolean handlePreferenceTreeClick(Preference preference) { 245 if (TextUtils.equals(preference.getKey(), getPreferenceKey())) { 246 if (enableOutputSwitcherForSystemRouting() && mMediaController == null) { 247 mContext.sendBroadcast(new Intent() 248 .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG) 249 .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)); 250 } else if (mMediaController != null) { 251 mContext.sendBroadcast(new Intent() 252 .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG) 253 .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME) 254 .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, 255 mMediaController.getPackageName()) 256 .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN, 257 mMediaController.getSessionToken())); 258 } 259 return true; 260 } 261 return false; 262 } 263 isDeviceBroadcasting()264 private boolean isDeviceBroadcasting() { 265 return com.android.settingslib.flags.Flags.enableLeAudioSharing() 266 && mLocalBluetoothLeBroadcast != null 267 && mLocalBluetoothLeBroadcast.isEnabled(null); 268 } 269 } 270