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