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