• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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