• 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 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