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