• 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.PAUSED;
20 import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.LocalBluetoothLeBroadcastSourceState.STREAMING;
21 import static com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant.getLocalSourceState;
22 
23 import android.app.settings.SettingsEnums;
24 import android.bluetooth.BluetoothDevice;
25 import android.bluetooth.BluetoothLeBroadcastAssistant;
26 import android.bluetooth.BluetoothLeBroadcastMetadata;
27 import android.bluetooth.BluetoothLeBroadcastReceiveState;
28 import android.content.Context;
29 import android.util.Log;
30 import android.view.View;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 import androidx.annotation.VisibleForTesting;
35 import androidx.lifecycle.DefaultLifecycleObserver;
36 import androidx.lifecycle.LifecycleOwner;
37 import androidx.preference.PreferenceScreen;
38 
39 import com.android.settings.R;
40 import com.android.settings.bluetooth.Utils;
41 import com.android.settings.core.BasePreferenceController;
42 import com.android.settings.overlay.FeatureFactory;
43 import com.android.settingslib.bluetooth.BluetoothUtils;
44 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastAssistant;
45 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
46 import com.android.settingslib.utils.ThreadUtils;
47 import com.android.settingslib.widget.ActionButtonsPreference;
48 
49 import java.util.concurrent.Executor;
50 import java.util.concurrent.Executors;
51 
52 public class AudioStreamButtonController extends BasePreferenceController
53         implements DefaultLifecycleObserver {
54     private static final String TAG = "AudioStreamButtonController";
55     private static final String KEY = "audio_stream_button";
56     private static final int SOURCE_ORIGIN_REPOSITORY = SourceOriginForLogging.REPOSITORY.ordinal();
57 
58     @VisibleForTesting
59     final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
60             new AudioStreamsBroadcastAssistantCallback() {
61                 @Override
62                 public void onSourceRemoved(BluetoothDevice sink, int sourceId, int reason) {
63                     super.onSourceRemoved(sink, sourceId, reason);
64                     updateButton();
65                 }
66 
67                 @Override
68                 public void onSourceRemoveFailed(BluetoothDevice sink, int sourceId, int reason) {
69                     super.onSourceRemoveFailed(sink, sourceId, reason);
70                     updateButton();
71                     mMetricsFeatureProvider.action(
72                             mContext, SettingsEnums.ACTION_AUDIO_STREAM_LEAVE_FAILED);
73                 }
74 
75                 @Override
76                 public void onReceiveStateChanged(
77                         BluetoothDevice sink,
78                         int sourceId,
79                         BluetoothLeBroadcastReceiveState state) {
80                     super.onReceiveStateChanged(sink, sourceId, state);
81                     var localSourceState = getLocalSourceState(state);
82                     boolean shouldUpdateButton = mHysteresisModeFixAvailable
83                             ? (localSourceState == PAUSED || localSourceState == STREAMING)
84                             : localSourceState == STREAMING;
85                     if (shouldUpdateButton) {
86                         updateButton();
87                         // TODO(b/308368124): Verify if this log is too noisy.
88                         mMetricsFeatureProvider.action(
89                                 mContext,
90                                 SettingsEnums.ACTION_AUDIO_STREAM_JOIN_SUCCEED,
91                                 SOURCE_ORIGIN_REPOSITORY);
92                     }
93                 }
94 
95                 @Override
96                 public void onSourceAddFailed(
97                         BluetoothDevice sink, BluetoothLeBroadcastMetadata source, int reason) {
98                     super.onSourceAddFailed(sink, source, reason);
99                     updateButton();
100                     mMetricsFeatureProvider.action(
101                             mContext,
102                             SettingsEnums.ACTION_AUDIO_STREAM_JOIN_FAILED_OTHER,
103                             SOURCE_ORIGIN_REPOSITORY);
104                 }
105 
106                 @Override
107                 public void onSourceLost(int broadcastId) {
108                     super.onSourceLost(broadcastId);
109                     updateButton();
110                 }
111             };
112 
113     private AudioStreamsRepository mAudioStreamsRepository = AudioStreamsRepository.getInstance();
114     private final Executor mExecutor;
115     private final AudioStreamsHelper mAudioStreamsHelper;
116     private final @Nullable LocalBluetoothLeBroadcastAssistant mLeBroadcastAssistant;
117     private final MetricsFeatureProvider mMetricsFeatureProvider;
118     private final boolean mHysteresisModeFixAvailable;
119     private @Nullable ActionButtonsPreference mPreference;
120     private int mBroadcastId = -1;
121 
AudioStreamButtonController(Context context, String preferenceKey)122     public AudioStreamButtonController(Context context, String preferenceKey) {
123         super(context, preferenceKey);
124         mExecutor = Executors.newSingleThreadExecutor();
125         mAudioStreamsHelper = new AudioStreamsHelper(Utils.getLocalBtManager(context));
126         mLeBroadcastAssistant = mAudioStreamsHelper.getLeBroadcastAssistant();
127         mHysteresisModeFixAvailable = BluetoothUtils.isAudioSharingHysteresisModeFixAvailable(
128                 context);
129         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
130     }
131 
132     @Override
onStart(@onNull LifecycleOwner owner)133     public void onStart(@NonNull LifecycleOwner owner) {
134         if (mLeBroadcastAssistant == null) {
135             Log.w(TAG, "onStart(): LeBroadcastAssistant is null!");
136             return;
137         }
138         mLeBroadcastAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
139     }
140 
141     @Override
onStop(@onNull LifecycleOwner owner)142     public void onStop(@NonNull LifecycleOwner owner) {
143         if (mLeBroadcastAssistant == null) {
144             Log.w(TAG, "onStop(): LeBroadcastAssistant is null!");
145             return;
146         }
147         mLeBroadcastAssistant.unregisterServiceCallBack(mBroadcastAssistantCallback);
148     }
149 
150     @Override
displayPreference(PreferenceScreen screen)151     public final void displayPreference(PreferenceScreen screen) {
152         mPreference = screen.findPreference(getPreferenceKey());
153         updateButton();
154         super.displayPreference(screen);
155     }
156 
updateButton()157     private void updateButton() {
158         if (mPreference == null) {
159             Log.w(TAG, "updateButton(): preference is null!");
160             return;
161         }
162 
163         boolean isConnected = mAudioStreamsHelper.getConnectedBroadcastIdAndState(
164                 mHysteresisModeFixAvailable).containsKey(mBroadcastId);
165 
166         View.OnClickListener onClickListener;
167 
168         if (isConnected) {
169             onClickListener =
170                     unused ->
171                             ThreadUtils.postOnBackgroundThread(
172                                     () -> {
173                                         mAudioStreamsHelper.removeSource(mBroadcastId);
174                                         mMetricsFeatureProvider.action(
175                                                 mContext,
176                                                 SettingsEnums
177                                                         .ACTION_AUDIO_STREAM_LEAVE_BUTTON_CLICK);
178                                         ThreadUtils.postOnMainThread(
179                                                 () -> {
180                                                     if (mPreference != null) {
181                                                         mPreference.setButton1Enabled(false);
182                                                     }
183                                                 });
184                                     });
185             ThreadUtils.postOnMainThread(
186                     () -> {
187                         if (mPreference != null) {
188                             mPreference.setButton1Enabled(true);
189                             mPreference
190                                     .setButton1Text(R.string.audio_streams_disconnect)
191                                     .setButton1Icon(
192                                             com.android.settings.R.drawable.ic_settings_close)
193                                     .setButton1OnClickListener(onClickListener);
194                         }
195                     });
196         } else {
197             onClickListener =
198                     unused ->
199                             ThreadUtils.postOnBackgroundThread(
200                                     () -> {
201                                         var metadata =
202                                                 mAudioStreamsRepository.getSavedMetadata(
203                                                         mContext, mBroadcastId);
204                                         if (metadata != null) {
205                                             mAudioStreamsHelper.addSource(metadata);
206                                             mMetricsFeatureProvider.action(
207                                                     mContext,
208                                                     SettingsEnums.ACTION_AUDIO_STREAM_JOIN,
209                                                     SOURCE_ORIGIN_REPOSITORY);
210                                             ThreadUtils.postOnMainThread(
211                                                     () -> {
212                                                         if (mPreference != null) {
213                                                             mPreference.setButton1Enabled(false);
214                                                         }
215                                                     });
216                                         }
217                                     });
218             ThreadUtils.postOnMainThread(
219                     () -> {
220                         if (mPreference != null) {
221                             mPreference.setButton1Enabled(true);
222                             mPreference
223                                     .setButton1Text(R.string.audio_streams_connect)
224                                     .setButton1Icon(com.android.settings.R.drawable.ic_add_24dp)
225                                     .setButton1OnClickListener(onClickListener);
226                         }
227                     });
228         }
229     }
230 
231     @Override
getAvailabilityStatus()232     public int getAvailabilityStatus() {
233         return AVAILABLE;
234     }
235 
236     @Override
getPreferenceKey()237     public String getPreferenceKey() {
238         return KEY;
239     }
240 
241     /** Initialize with broadcast id */
init(int broadcastId)242     void init(int broadcastId) {
243         mBroadcastId = broadcastId;
244     }
245 
246     @VisibleForTesting
setAudioStreamsRepositoryForTesting(AudioStreamsRepository repository)247     void setAudioStreamsRepositoryForTesting(AudioStreamsRepository repository) {
248         mAudioStreamsRepository = repository;
249     }
250 }
251