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