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