1 /* 2 * Copyright (C) 2023 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.connecteddevice.audiosharing.audiostreams; 18 19 import android.bluetooth.BluetoothDevice; 20 import android.bluetooth.BluetoothLeBroadcastAssistant; 21 import android.bluetooth.BluetoothLeBroadcastReceiveState; 22 import android.content.Context; 23 import android.util.Log; 24 25 import androidx.annotation.NonNull; 26 import androidx.annotation.VisibleForTesting; 27 import androidx.lifecycle.DefaultLifecycleObserver; 28 import androidx.lifecycle.LifecycleOwner; 29 import androidx.preference.PreferenceScreen; 30 31 import com.android.settings.R; 32 import com.android.settings.bluetooth.Utils; 33 import com.android.settings.core.BasePreferenceController; 34 import com.android.settings.dashboard.DashboardFragment; 35 import com.android.settings.widget.EntityHeaderController; 36 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant; 37 import com.android.settingslib.utils.ThreadUtils; 38 import com.android.settingslib.widget.LayoutPreference; 39 40 import java.util.concurrent.Executor; 41 import java.util.concurrent.Executors; 42 43 import javax.annotation.Nullable; 44 45 public class AudioStreamHeaderController extends BasePreferenceController 46 implements DefaultLifecycleObserver { 47 @VisibleForTesting 48 static final int AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY = 49 R.string.audio_streams_listening_now; 50 51 @VisibleForTesting static final String AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY = ""; 52 private static final String TAG = "AudioStreamHeaderController"; 53 private static final String KEY = "audio_stream_header"; 54 private final Executor mExecutor; 55 private final AudioStreamsHelper mAudioStreamsHelper; 56 @Nullable private final LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant; 57 private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback = 58 new AudioStreamsBroadcastAssistantCallback() { 59 @Override 60 public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) { 61 super.onSourceRemoved(sink, sourceId, reason); 62 updateSummary(); 63 } 64 65 @Override 66 public void onSourceLost(int broadcastId) { 67 super.onSourceLost(broadcastId); 68 updateSummary(); 69 } 70 71 @Override 72 public void onReceiveStateChanged( 73 BluetoothDevice sink, 74 int sourceId, 75 BluetoothLeBroadcastReceiveState state) { 76 super.onReceiveStateChanged(sink, sourceId, state); 77 if (AudioStreamsHelper.isConnected(state)) { 78 updateSummary(); 79 mAudioStreamsHelper.startMediaService( 80 mContext, mBroadcastId, mBroadcastName); 81 } 82 } 83 }; 84 85 private @Nullable EntityHeaderController mHeaderController; 86 private @Nullable DashboardFragment mFragment; 87 private String mBroadcastName = ""; 88 private int mBroadcastId = -1; 89 AudioStreamHeaderController(Context context, String preferenceKey)90 public AudioStreamHeaderController(Context context, String preferenceKey) { 91 super(context, preferenceKey); 92 mExecutor = Executors.newSingleThreadExecutor(); 93 mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context)); 94 mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant(); 95 } 96 97 @Override onStart(@onNull LifecycleOwner owner)98 public void onStart(@NonNull LifecycleOwner owner) { 99 if (mLeBroadcastAssistant == null) { 100 Log.w(TAG, "onStart(): LeBroadcastAssistant is null!"); 101 return; 102 } 103 mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback); 104 } 105 106 @Override onStop(@onNull LifecycleOwner owner)107 public void onStop(@NonNull LifecycleOwner owner) { 108 if (mLeBroadcastAssistant == null) { 109 Log.w(TAG, "onStop(): LeBroadcastAssistant is null!"); 110 return; 111 } 112 mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback); 113 } 114 115 @Override displayPreference(PreferenceScreen screen)116 public final void displayPreference(PreferenceScreen screen) { 117 LayoutPreference headerPreference = screen.findPreference(KEY); 118 if (headerPreference != null && mFragment != null) { 119 mHeaderController = 120 EntityHeaderController.newInstance( 121 mFragment.getActivity(), 122 mFragment, 123 headerPreference.findViewById(com.android.settings.R.id.entity_header)); 124 if (mBroadcastName != null) { 125 mHeaderController.setLabel(mBroadcastName); 126 } 127 mHeaderController.setIcon( 128 screen.getContext() 129 .getDrawable( 130 com.android.settingslib.R.drawable.ic_bt_le_audio_sharing)); 131 screen.addPreference(headerPreference); 132 updateSummary(); 133 } 134 super.displayPreference(screen); 135 } 136 updateSummary()137 private void updateSummary() { 138 var unused = 139 ThreadUtils.postOnBackgroundThread( 140 () -> { 141 var latestSummary = 142 mAudioStreamsHelper.getAllConnectedSources().stream() 143 .map( 144 BluetoothLeBroadcastReceiveState 145 ::getBroadcastId) 146 .anyMatch( 147 connectedBroadcastId -> 148 connectedBroadcastId 149 == mBroadcastId) 150 ? mContext.getString( 151 AUDIO_STREAM_HEADER_LISTENING_NOW_SUMMARY) 152 : AUDIO_STREAM_HEADER_NOT_LISTENING_SUMMARY; 153 ThreadUtils.postOnMainThread( 154 () -> { 155 if (mHeaderController != null) { 156 mHeaderController.setSummary(latestSummary); 157 mHeaderController.done(true); 158 } 159 }); 160 }); 161 } 162 163 @Override getAvailabilityStatus()164 public int getAvailabilityStatus() { 165 return AVAILABLE; 166 } 167 168 @Override getPreferenceKey()169 public String getPreferenceKey() { 170 return KEY; 171 } 172 173 /** Initialize with {@link AudioStreamDetailsFragment} and broadcast name and id */ init( AudioStreamDetailsFragment audioStreamDetailsFragment, String broadcastName, int broadcastId)174 void init( 175 AudioStreamDetailsFragment audioStreamDetailsFragment, 176 String broadcastName, 177 int broadcastId) { 178 mFragment = audioStreamDetailsFragment; 179 mBroadcastName = broadcastName; 180 mBroadcastId = broadcastId; 181 } 182 } 183