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