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 45 import java.util.List; 46 47 /** 48 * This class allows launching MediaOutputDialog to switch output device. 49 * Preference would hide only when 50 * - Bluetooth = OFF 51 * - Bluetooth = ON and Connected Devices = 0 and Previously Connected = 0 52 * - Media stream captured by remote device 53 * - During a call. 54 */ 55 public class MediaOutputPreferenceController extends AudioSwitchPreferenceController { 56 57 private static final String TAG = "MediaOutputPreferenceController"; 58 @Nullable private MediaController mMediaController; 59 private MediaSessionManager mMediaSessionManager; 60 61 @Nullable private LocalBluetoothLeBroadcast mLocalBluetoothLeBroadcast; 62 63 private final BluetoothLeBroadcast.Callback mBroadcastCallback = 64 new BluetoothLeBroadcast.Callback() { 65 @Override 66 public void onBroadcastStarted(int reason, int broadcastId) { 67 updateState(mPreference); 68 } 69 70 @Override 71 public void onBroadcastStartFailed(int reason) { 72 updateState(mPreference); 73 } 74 75 @Override 76 public void onBroadcastStopped(int reason, int broadcastId) { 77 updateState(mPreference); 78 } 79 80 @Override 81 public void onBroadcastStopFailed(int reason) { 82 updateState(mPreference); 83 } 84 85 @Override 86 public void onPlaybackStarted(int reason, int broadcastId) {} 87 88 @Override 89 public void onPlaybackStopped(int reason, int broadcastId) {} 90 91 @Override 92 public void onBroadcastUpdated(int reason, int broadcastId) {} 93 94 @Override 95 public void onBroadcastUpdateFailed(int reason, int broadcastId) {} 96 97 @Override 98 public void onBroadcastMetadataChanged( 99 int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {} 100 }; 101 MediaOutputPreferenceController(Context context, String key)102 public MediaOutputPreferenceController(Context context, String key) { 103 super(context, key); 104 mMediaSessionManager = context.getSystemService(MediaSessionManager.class); 105 mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager); 106 LocalBluetoothManager localBluetoothManager = 107 com.android.settings.bluetooth.Utils.getLocalBtManager(mContext); 108 if (localBluetoothManager != null) { 109 mLocalBluetoothLeBroadcast = 110 localBluetoothManager.getProfileManager().getLeAudioBroadcastProfile(); 111 } 112 } 113 114 @Override onStart()115 public void onStart() { 116 super.onStart(); 117 if (mLocalBluetoothLeBroadcast != null) { 118 mLocalBluetoothLeBroadcast.registerServiceCallBack( 119 mContext.getMainExecutor(), mBroadcastCallback); 120 } 121 } 122 123 @Override onStop()124 public void onStop() { 125 super.onStop(); 126 if (mLocalBluetoothLeBroadcast != null) { 127 mLocalBluetoothLeBroadcast.unregisterServiceCallBack(mBroadcastCallback); 128 } 129 } 130 131 @Override displayPreference(PreferenceScreen screen)132 public void displayPreference(PreferenceScreen screen) { 133 super.displayPreference(screen); 134 135 mPreference.setVisible(!Utils.isAudioModeOngoingCall(mContext) 136 && (enableOutputSwitcherForSystemRouting() ? true : mMediaController != null)); 137 } 138 139 @Override updateState(Preference preference)140 public void updateState(Preference preference) { 141 if (preference == null) { 142 // In case UI is not ready. 143 return; 144 } 145 146 if (enableOutputSwitcherForSystemRouting()) { 147 mMediaController = MediaOutputUtils.getActiveLocalMediaController(mMediaSessionManager); 148 } else { 149 if (mMediaController == null) { 150 // No active local playback 151 return; 152 } 153 } 154 155 mPreference.setEnabled(true); 156 if (Utils.isAudioModeOngoingCall(mContext)) { 157 // Ongoing call status, switch entry for media will be disabled. 158 mPreference.setVisible(false); 159 preference.setSummary( 160 mContext.getText(R.string.media_out_summary_ongoing_call_state)); 161 return; 162 } 163 164 BluetoothDevice activeDevice = null; 165 // Show preference if there is connected or previously connected device 166 // Find active device and set its name as the preference's summary 167 List<BluetoothDevice> connectedA2dpDevices = getConnectedA2dpDevices(); 168 List<BluetoothDevice> connectedHADevices = getConnectedHearingAidDevices(); 169 List<BluetoothDevice> connectedLeAudioDevices = getConnectedLeAudioDevices(); 170 if (mAudioManager.getMode() == AudioManager.MODE_NORMAL 171 && ((connectedA2dpDevices != null && !connectedA2dpDevices.isEmpty()) 172 || (connectedHADevices != null && !connectedHADevices.isEmpty()) 173 || (connectedLeAudioDevices != null && !connectedLeAudioDevices.isEmpty()))) { 174 activeDevice = findActiveDevice(); 175 } 176 177 if (mMediaController == null) { 178 mPreference.setTitle(mContext.getString(R.string.media_output_title_without_playing)); 179 } else { 180 mPreference.setTitle(mContext.getString(R.string.media_output_label_title, 181 com.android.settings.Utils.getApplicationLabel(mContext, 182 mMediaController.getPackageName()))); 183 } 184 if (isDeviceBroadcasting()) { 185 mPreference.setSummary(R.string.media_output_audio_sharing); 186 mPreference.setEnabled(false); 187 } else { 188 mPreference.setSummary( 189 (activeDevice == null) 190 ? mContext.getText(R.string.media_output_default_summary) 191 : activeDevice.getAlias()); 192 } 193 } 194 195 @Override findActiveDevice()196 public BluetoothDevice findActiveDevice() { 197 BluetoothDevice haActiveDevice = findActiveHearingAidDevice(); 198 BluetoothDevice leAudioActiveDevice = findActiveLeAudioDevice(); 199 final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); 200 201 if (haActiveDevice != null) { 202 return haActiveDevice; 203 } 204 205 if (leAudioActiveDevice != null) { 206 return leAudioActiveDevice; 207 } 208 209 if (a2dpProfile != null && a2dpProfile.getActiveDevice() != null) { 210 return a2dpProfile.getActiveDevice(); 211 } 212 213 return null; 214 } 215 216 /** 217 * Find active hearing aid device 218 */ 219 @Override findActiveHearingAidDevice()220 protected BluetoothDevice findActiveHearingAidDevice() { 221 final HearingAidProfile hearingAidProfile = mProfileManager.getHearingAidProfile(); 222 223 if (hearingAidProfile != null) { 224 List<BluetoothDevice> activeDevices = hearingAidProfile.getActiveDevices(); 225 for (BluetoothDevice btDevice : activeDevices) { 226 if (btDevice != null) { 227 return btDevice; 228 } 229 } 230 } 231 return null; 232 } 233 234 @Override handlePreferenceTreeClick(Preference preference)235 public boolean handlePreferenceTreeClick(Preference preference) { 236 if (TextUtils.equals(preference.getKey(), getPreferenceKey())) { 237 if (enableOutputSwitcherForSystemRouting() && mMediaController == null) { 238 mContext.sendBroadcast(new Intent() 239 .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG) 240 .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)); 241 } else if (mMediaController != null) { 242 mContext.sendBroadcast(new Intent() 243 .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG) 244 .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME) 245 .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME, 246 mMediaController.getPackageName()) 247 .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN, 248 mMediaController.getSessionToken())); 249 } 250 return true; 251 } 252 return false; 253 } 254 isDeviceBroadcasting()255 private boolean isDeviceBroadcasting() { 256 return com.android.settingslib.flags.Flags.enableLeAudioSharing() 257 && mLocalBluetoothLeBroadcast != null 258 && mLocalBluetoothLeBroadcast.isEnabled(null); 259 } 260 } 261