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