1 /* 2 * Copyright (C) 2014 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 package com.android.bluetooth.a2dpsink; 17 18 import static android.Manifest.permission.BLUETOOTH_CONNECT; 19 20 import android.annotation.RequiresPermission; 21 import android.bluetooth.BluetoothA2dpSink; 22 import android.bluetooth.BluetoothAudioConfig; 23 import android.bluetooth.BluetoothDevice; 24 import android.bluetooth.BluetoothProfile; 25 import android.content.Intent; 26 import android.media.AudioFormat; 27 import android.os.Message; 28 import android.util.Log; 29 30 import com.android.bluetooth.BluetoothMetricsProto; 31 import com.android.bluetooth.Utils; 32 import com.android.bluetooth.btservice.MetricsLogger; 33 import com.android.bluetooth.btservice.ProfileService; 34 import com.android.bluetooth.statemachine.State; 35 import com.android.bluetooth.statemachine.StateMachine; 36 37 38 public class A2dpSinkStateMachine extends StateMachine { 39 static final String TAG = "A2DPSinkStateMachine"; 40 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 41 42 //0->99 Events from Outside 43 public static final int CONNECT = 1; 44 public static final int DISCONNECT = 2; 45 46 //100->199 Internal Events 47 protected static final int CLEANUP = 100; 48 private static final int CONNECT_TIMEOUT = 101; 49 50 //200->299 Events from Native 51 static final int STACK_EVENT = 200; 52 53 static final int CONNECT_TIMEOUT_MS = 5000; 54 55 protected final BluetoothDevice mDevice; 56 protected final byte[] mDeviceAddress; 57 protected final A2dpSinkService mService; 58 protected final Disconnected mDisconnected; 59 protected final Connecting mConnecting; 60 protected final Connected mConnected; 61 protected final Disconnecting mDisconnecting; 62 63 protected int mMostRecentState = BluetoothProfile.STATE_DISCONNECTED; 64 protected BluetoothAudioConfig mAudioConfig = null; 65 A2dpSinkStateMachine(BluetoothDevice device, A2dpSinkService service)66 A2dpSinkStateMachine(BluetoothDevice device, A2dpSinkService service) { 67 super(TAG); 68 mDevice = device; 69 mDeviceAddress = Utils.getByteAddress(mDevice); 70 mService = service; 71 if (DBG) Log.d(TAG, device.toString()); 72 73 mDisconnected = new Disconnected(); 74 mConnecting = new Connecting(); 75 mConnected = new Connected(); 76 mDisconnecting = new Disconnecting(); 77 78 addState(mDisconnected); 79 addState(mConnecting); 80 addState(mConnected); 81 addState(mDisconnecting); 82 83 setInitialState(mDisconnected); 84 } 85 getConnectionStateChangedIntent()86 protected String getConnectionStateChangedIntent() { 87 return BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED; 88 } 89 90 /** 91 * Get the current connection state 92 * 93 * @return current State 94 */ getState()95 public int getState() { 96 return mMostRecentState; 97 } 98 99 /** 100 * get current audio config 101 */ getAudioConfig()102 BluetoothAudioConfig getAudioConfig() { 103 return mAudioConfig; 104 } 105 106 /** 107 * Get the underlying device tracked by this state machine 108 * 109 * @return device in focus 110 */ getDevice()111 public synchronized BluetoothDevice getDevice() { 112 return mDevice; 113 } 114 115 /** 116 * send the Connect command asynchronously 117 */ connect()118 public final void connect() { 119 sendMessage(CONNECT); 120 } 121 122 /** 123 * send the Disconnect command asynchronously 124 */ disconnect()125 public final void disconnect() { 126 sendMessage(DISCONNECT); 127 } 128 129 /** 130 * Dump the current State Machine to the string builder. 131 * @param sb output string 132 */ dump(StringBuilder sb)133 public void dump(StringBuilder sb) { 134 ProfileService.println(sb, "mDevice: " + mDevice.getAddress() + "(" 135 + Utils.getName(mDevice) + ") " + this.toString()); 136 } 137 138 @Override unhandledMessage(Message msg)139 protected void unhandledMessage(Message msg) { 140 Log.w(TAG, "unhandledMessage in state " + getCurrentState() + "msg.what=" + msg.what); 141 } 142 143 class Disconnected extends State { 144 @Override enter()145 public void enter() { 146 if (DBG) Log.d(TAG, "Enter Disconnected"); 147 if (mMostRecentState != BluetoothProfile.STATE_DISCONNECTED) { 148 sendMessage(CLEANUP); 149 } 150 onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTED); 151 } 152 153 @Override processMessage(Message message)154 public boolean processMessage(Message message) { 155 switch (message.what) { 156 case STACK_EVENT: 157 processStackEvent((StackEvent) message.obj); 158 return true; 159 case CONNECT: 160 if (DBG) Log.d(TAG, "Connect"); 161 transitionTo(mConnecting); 162 return true; 163 case CLEANUP: 164 mService.removeStateMachine(A2dpSinkStateMachine.this); 165 return true; 166 } 167 return false; 168 } 169 170 @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) processStackEvent(StackEvent event)171 void processStackEvent(StackEvent event) { 172 switch (event.mType) { 173 case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 174 switch (event.mState) { 175 case StackEvent.CONNECTION_STATE_CONNECTING: 176 if (mService.getConnectionPolicy(mDevice) 177 == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN) { 178 Log.w(TAG, "Ignore incoming connection, profile is" 179 + " turned off for " + mDevice); 180 mService.disconnectA2dpNative(mDeviceAddress); 181 } else { 182 mConnecting.mIncomingConnection = true; 183 transitionTo(mConnecting); 184 } 185 break; 186 case StackEvent.CONNECTION_STATE_CONNECTED: 187 transitionTo(mConnected); 188 break; 189 case StackEvent.CONNECTION_STATE_DISCONNECTED: 190 sendMessage(CLEANUP); 191 break; 192 } 193 } 194 } 195 } 196 197 class Connecting extends State { 198 boolean mIncomingConnection = false; 199 200 @Override enter()201 public void enter() { 202 if (DBG) Log.d(TAG, "Enter Connecting"); 203 onConnectionStateChanged(BluetoothProfile.STATE_CONNECTING); 204 sendMessageDelayed(CONNECT_TIMEOUT, CONNECT_TIMEOUT_MS); 205 206 if (!mIncomingConnection) { 207 mService.connectA2dpNative(mDeviceAddress); 208 } 209 210 super.enter(); 211 } 212 213 @Override processMessage(Message message)214 public boolean processMessage(Message message) { 215 switch (message.what) { 216 case STACK_EVENT: 217 processStackEvent((StackEvent) message.obj); 218 return true; 219 case CONNECT_TIMEOUT: 220 transitionTo(mDisconnected); 221 return true; 222 } 223 return false; 224 } 225 processStackEvent(StackEvent event)226 void processStackEvent(StackEvent event) { 227 switch (event.mType) { 228 case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 229 switch (event.mState) { 230 case StackEvent.CONNECTION_STATE_CONNECTED: 231 transitionTo(mConnected); 232 break; 233 case StackEvent.CONNECTION_STATE_DISCONNECTED: 234 transitionTo(mDisconnected); 235 break; 236 } 237 } 238 } 239 @Override exit()240 public void exit() { 241 removeMessages(CONNECT_TIMEOUT); 242 mIncomingConnection = false; 243 } 244 245 } 246 247 class Connected extends State { 248 @Override enter()249 public void enter() { 250 if (DBG) Log.d(TAG, "Enter Connected"); 251 onConnectionStateChanged(BluetoothProfile.STATE_CONNECTED); 252 } 253 254 @Override processMessage(Message message)255 public boolean processMessage(Message message) { 256 switch (message.what) { 257 case DISCONNECT: 258 transitionTo(mDisconnecting); 259 mService.disconnectA2dpNative(mDeviceAddress); 260 return true; 261 case STACK_EVENT: 262 processStackEvent((StackEvent) message.obj); 263 return true; 264 } 265 return false; 266 } 267 processStackEvent(StackEvent event)268 void processStackEvent(StackEvent event) { 269 switch (event.mType) { 270 case StackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED: 271 switch (event.mState) { 272 case StackEvent.CONNECTION_STATE_DISCONNECTING: 273 transitionTo(mDisconnecting); 274 break; 275 case StackEvent.CONNECTION_STATE_DISCONNECTED: 276 transitionTo(mDisconnected); 277 break; 278 } 279 break; 280 case StackEvent.EVENT_TYPE_AUDIO_CONFIG_CHANGED: 281 mAudioConfig = new BluetoothAudioConfig(event.mSampleRate, event.mChannelCount, 282 AudioFormat.ENCODING_PCM_16BIT); 283 break; 284 } 285 } 286 } 287 288 protected class Disconnecting extends State { 289 @Override enter()290 public void enter() { 291 if (DBG) Log.d(TAG, "Enter Disconnecting"); 292 onConnectionStateChanged(BluetoothProfile.STATE_DISCONNECTING); 293 transitionTo(mDisconnected); 294 } 295 } 296 onConnectionStateChanged(int currentState)297 protected void onConnectionStateChanged(int currentState) { 298 if (mMostRecentState == currentState) { 299 return; 300 } 301 if (currentState == BluetoothProfile.STATE_CONNECTED) { 302 MetricsLogger.logProfileConnectionEvent(BluetoothMetricsProto.ProfileId.A2DP_SINK); 303 } 304 if (DBG) { 305 Log.d(TAG, "Connection state " + mDevice + ": " + mMostRecentState + "->" 306 + currentState); 307 } 308 Intent intent = new Intent(BluetoothA2dpSink.ACTION_CONNECTION_STATE_CHANGED); 309 intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, mMostRecentState); 310 intent.putExtra(BluetoothProfile.EXTRA_STATE, currentState); 311 intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mDevice); 312 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 313 mMostRecentState = currentState; 314 mService.sendBroadcast(intent, BLUETOOTH_CONNECT, Utils.getTempAllowlistBroadcastOptions()); 315 } 316 } 317