• 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;
18 
19 import static com.android.settingslib.bluetooth.BluetoothUtils.isBroadcasting;
20 
21 import android.app.settings.SettingsEnums;
22 import android.bluetooth.BluetoothLeBroadcast;
23 import android.bluetooth.BluetoothLeBroadcastMetadata;
24 import android.content.Context;
25 import android.util.Log;
26 
27 import androidx.annotation.NonNull;
28 import androidx.annotation.Nullable;
29 import androidx.annotation.VisibleForTesting;
30 import androidx.lifecycle.DefaultLifecycleObserver;
31 import androidx.lifecycle.LifecycleOwner;
32 import androidx.preference.Preference;
33 import androidx.preference.PreferenceScreen;
34 
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.settings.widget.ValidatedEditTextPreference;
39 import com.android.settingslib.bluetooth.BluetoothUtils;
40 import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
41 import com.android.settingslib.bluetooth.LocalBluetoothManager;
42 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
43 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
44 import com.android.settingslib.utils.ThreadUtils;
45 
46 import java.util.concurrent.Executor;
47 import java.util.concurrent.Executors;
48 import java.util.concurrent.atomic.AtomicBoolean;
49 
50 public class AudioSharingNamePreferenceController extends BasePreferenceController
51         implements ValidatedEditTextPreference.Validator,
52                 Preference.OnPreferenceChangeListener,
53                 DefaultLifecycleObserver,
54                 LocalBluetoothProfileManager.ServiceListener {
55 
56     private static final String TAG = "AudioSharingNamePreferenceController";
57     private static final boolean DEBUG = BluetoothUtils.D;
58     private static final String PREF_KEY = "audio_sharing_stream_name";
59 
60     @VisibleForTesting
61     final BluetoothLeBroadcast.Callback mBroadcastCallback =
62             new BluetoothLeBroadcast.Callback() {
63                 @Override
64                 public void onBroadcastMetadataChanged(
65                         int broadcastId, BluetoothLeBroadcastMetadata metadata) {
66                     if (DEBUG) {
67                         Log.d(
68                                 TAG,
69                                 "onBroadcastMetadataChanged() broadcastId : "
70                                         + broadcastId
71                                         + " metadata: "
72                                         + metadata);
73                     }
74                     updateQrCodeIcon(true);
75                 }
76 
77                 @Override
78                 public void onBroadcastStartFailed(int reason) {}
79 
80                 @Override
81                 public void onBroadcastStarted(int reason, int broadcastId) {}
82 
83                 @Override
84                 public void onBroadcastStopFailed(int reason) {}
85 
86                 @Override
87                 public void onBroadcastStopped(int reason, int broadcastId) {
88                     if (DEBUG) {
89                         Log.d(
90                                 TAG,
91                                 "onBroadcastStopped() reason : "
92                                         + reason
93                                         + " broadcastId: "
94                                         + broadcastId);
95                     }
96                     updateQrCodeIcon(false);
97                 }
98 
99                 @Override
100                 public void onBroadcastUpdateFailed(int reason, int broadcastId) {
101                     Log.w(TAG, "onBroadcastUpdateFailed() reason : " + reason);
102                 }
103 
104                 @Override
105                 public void onBroadcastUpdated(int reason, int broadcastId) {
106                     if (DEBUG) {
107                         Log.d(TAG, "onBroadcastUpdated() reason : " + reason);
108                     }
109                 }
110 
111                 @Override
112                 public void onPlaybackStarted(int reason, int broadcastId) {}
113 
114                 @Override
115                 public void onPlaybackStopped(int reason, int broadcastId) {}
116             };
117 
118     @Nullable private final LocalBluetoothManager mBtManager;
119     @Nullable private final LocalBluetoothProfileManager mProfileManager;
120     @Nullable private final LocalBluetoothLeBroadcast mBroadcast;
121     @Nullable private AudioSharingNamePreference mPreference;
122     private final Executor mExecutor;
123     private final AudioSharingNameTextValidator mAudioSharingNameTextValidator;
124 
125     private final MetricsFeatureProvider mMetricsFeatureProvider;
126     private AtomicBoolean mCallbacksRegistered = new AtomicBoolean(false);
127 
AudioSharingNamePreferenceController(Context context, String preferenceKey)128     public AudioSharingNamePreferenceController(Context context, String preferenceKey) {
129         super(context, preferenceKey);
130         mBtManager = Utils.getLocalBluetoothManager(context);
131         mProfileManager = mBtManager == null ? null : mBtManager.getProfileManager();
132         mBroadcast =
133                 (mProfileManager != null) ? mProfileManager.getLeAudioBroadcastProfile() : null;
134         mAudioSharingNameTextValidator = new AudioSharingNameTextValidator();
135         mExecutor = Executors.newSingleThreadExecutor();
136         mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
137     }
138 
139     @Override
onStart(@onNull LifecycleOwner owner)140     public void onStart(@NonNull LifecycleOwner owner) {
141         if (!isAvailable()) {
142             Log.d(TAG, "Skip register callbacks, feature not support");
143             return;
144         }
145         if (!AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
146             Log.d(TAG, "Skip register callbacks, profile not ready");
147             if (mProfileManager != null) {
148                 mProfileManager.addServiceListener(this);
149             }
150             return;
151         }
152         registerCallbacks();
153     }
154 
155     @Override
onStop(@onNull LifecycleOwner owner)156     public void onStop(@NonNull LifecycleOwner owner) {
157         if (!isAvailable()) {
158             Log.d(TAG, "Skip unregister callbacks, feature not support");
159             return;
160         }
161         if (mProfileManager != null) {
162             mProfileManager.removeServiceListener(this);
163         }
164         if (mBroadcast == null || !AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
165             Log.d(TAG, "Skip unregister callbacks, profile not ready");
166             return;
167         }
168         if (mCallbacksRegistered.get()) {
169             Log.d(TAG, "Unregister callbacks");
170             mBroadcast.unregisterServiceCallBack(mBroadcastCallback);
171             mCallbacksRegistered.set(false);
172         }
173     }
174 
175     @Override
getAvailabilityStatus()176     public int getAvailabilityStatus() {
177         return BluetoothUtils.isAudioSharingUIAvailable(mContext) ? AVAILABLE
178                 : UNSUPPORTED_ON_DEVICE;
179     }
180 
181     @Override
displayPreference(PreferenceScreen screen)182     public void displayPreference(PreferenceScreen screen) {
183         super.displayPreference(screen);
184         mPreference = screen.findPreference(getPreferenceKey());
185         if (mPreference != null) {
186             mPreference.setValidator(this);
187             updateBroadcastName();
188             updateQrCodeIcon(isBroadcasting(mBtManager));
189         }
190     }
191 
192     @Override
onServiceConnected()193     public void onServiceConnected() {
194         if (AudioSharingUtils.isAudioSharingProfileReady(mProfileManager)) {
195             registerCallbacks();
196             updateBroadcastName();
197             updateQrCodeIcon(isBroadcasting(mBtManager));
198             if (mProfileManager != null) {
199                 mProfileManager.removeServiceListener(this);
200             }
201         }
202     }
203 
204     @Override
onServiceDisconnected()205     public void onServiceDisconnected() {
206         // Do nothing
207     }
208 
209     @Override
getPreferenceKey()210     public String getPreferenceKey() {
211         return PREF_KEY;
212     }
213 
214     @Override
onPreferenceChange(Preference preference, Object newValue)215     public boolean onPreferenceChange(Preference preference, Object newValue) {
216         if (mPreference != null
217                 && mPreference.getSummary() != null
218                 && ((String) newValue).contentEquals(mPreference.getSummary())) {
219             return false;
220         }
221 
222         var unused =
223                 ThreadUtils.postOnBackgroundThread(
224                         () -> {
225                             if (mBroadcast != null) {
226                                 boolean isBroadcasting = isBroadcasting(mBtManager);
227                                 mBroadcast.setBroadcastName((String) newValue);
228                                 // We currently don't have a UI field for program info so we keep it
229                                 // consistent with broadcast name.
230                                 mBroadcast.setProgramInfo((String) newValue);
231                                 if (isBroadcasting) {
232                                     mBroadcast.updateBroadcast();
233                                 }
234                                 updateBroadcastName();
235                                 mMetricsFeatureProvider.action(
236                                         mContext,
237                                         SettingsEnums.ACTION_AUDIO_STREAM_NAME_UPDATED,
238                                         isBroadcasting ? 1 : 0);
239                             }
240                         });
241         return true;
242     }
243 
registerCallbacks()244     private void registerCallbacks() {
245         if (mBroadcast == null) {
246             Log.d(TAG, "Skip register callbacks, profile not ready");
247             return;
248         }
249         if (!mCallbacksRegistered.get()) {
250             Log.d(TAG, "Register callbacks");
251             mBroadcast.registerServiceCallBack(mExecutor, mBroadcastCallback);
252             mCallbacksRegistered.set(true);
253         }
254     }
255 
updateBroadcastName()256     private void updateBroadcastName() {
257         if (mPreference != null) {
258             var unused =
259                     ThreadUtils.postOnBackgroundThread(
260                             () -> {
261                                 if (mBroadcast != null) {
262                                     String name = mBroadcast.getBroadcastName();
263                                     AudioSharingUtils.postOnMainThread(
264                                             mContext,
265                                             () -> {
266                                                 if (mPreference != null) {
267                                                     mPreference.setText(name);
268                                                     mPreference.setSummary(name);
269                                                 }
270                                             });
271                                 }
272                             });
273         }
274     }
275 
updateQrCodeIcon(boolean show)276     private void updateQrCodeIcon(boolean show) {
277         if (mPreference != null) {
278             AudioSharingUtils.postOnMainThread(
279                     mContext,
280                     () -> {
281                         if (mPreference != null) {
282                             mPreference.setShowQrCodeIcon(show);
283                         }
284                     });
285         }
286     }
287 
288     @Override
isTextValid(String value)289     public boolean isTextValid(String value) {
290         return mAudioSharingNameTextValidator.isTextValid(value);
291     }
292 }
293