1/*
2 * Copyright (C) 2010 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 */
16package android.bluetooth;
17
18import android.content.BroadcastReceiver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.os.Message;
23import android.util.Log;
24
25import com.android.internal.util.State;
26import com.android.internal.util.StateMachine;
27
28/**
29 * This state machine is used to serialize the connections
30 * to a particular profile. Currently, we only allow one device
31 * to be connected to a particular profile.
32 * States:
33 *      {@link StableState} : No pending commands. Send the
34 *      command to the appropriate remote device specific state machine.
35 *
36 *      {@link PendingCommandState} : A profile connection / disconnection
37 *      command is being executed. This will result in a profile state
38 *      change. Defer all commands.
39 * @hide
40 */
41
42public class BluetoothProfileState extends StateMachine {
43    private static final boolean DBG = true;
44    private static final String TAG = "BluetoothProfileState";
45
46    public static final int HFP = 0;
47    public static final int A2DP = 1;
48    public static final int HID = 2;
49
50    static final int TRANSITION_TO_STABLE = 100;
51
52    private int mProfile;
53    private BluetoothDevice mPendingDevice;
54    private PendingCommandState mPendingCommandState = new PendingCommandState();
55    private StableState mStableState = new StableState();
56
57    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
58        @Override
59        public void onReceive(Context context, Intent intent) {
60            String action = intent.getAction();
61            BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
62            if (device == null) {
63                return;
64            }
65            if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
66                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
67                if (mProfile == HFP && (newState == BluetoothProfile.STATE_CONNECTED ||
68                    newState == BluetoothProfile.STATE_DISCONNECTED)) {
69                    sendMessage(TRANSITION_TO_STABLE);
70                }
71            } else if (action.equals(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED)) {
72                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
73                if (mProfile == A2DP && (newState == BluetoothProfile.STATE_CONNECTED ||
74                    newState == BluetoothProfile.STATE_DISCONNECTED)) {
75                    sendMessage(TRANSITION_TO_STABLE);
76                }
77            } else if (action.equals(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED)) {
78                int newState = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, 0);
79                if (mProfile == HID && (newState == BluetoothProfile.STATE_CONNECTED ||
80                    newState == BluetoothProfile.STATE_DISCONNECTED)) {
81                    sendMessage(TRANSITION_TO_STABLE);
82                }
83            } else if (action.equals(BluetoothDevice.ACTION_ACL_DISCONNECTED)) {
84                if (device.equals(mPendingDevice)) {
85                    sendMessage(TRANSITION_TO_STABLE);
86                }
87            }
88        }
89    };
90
91    public BluetoothProfileState(Context context, int profile) {
92        super("BluetoothProfileState:" + profile);
93        mProfile = profile;
94        addState(mStableState);
95        addState(mPendingCommandState);
96        setInitialState(mStableState);
97
98        IntentFilter filter = new IntentFilter();
99        filter.addAction(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED);
100        filter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
101        filter.addAction(BluetoothInputDevice.ACTION_CONNECTION_STATE_CHANGED);
102        filter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED);
103        context.registerReceiver(mBroadcastReceiver, filter);
104    }
105
106    private class StableState extends State {
107        @Override
108        public void enter() {
109            log("Entering Stable State");
110            mPendingDevice = null;
111        }
112
113        @Override
114        public boolean processMessage(Message msg) {
115            if (msg.what != TRANSITION_TO_STABLE) {
116                transitionTo(mPendingCommandState);
117            }
118            return true;
119        }
120    }
121
122    private class PendingCommandState extends State {
123        @Override
124        public void enter() {
125            log("Entering PendingCommandState State");
126            dispatchMessage(getCurrentMessage());
127        }
128
129        @Override
130        public boolean processMessage(Message msg) {
131            if (msg.what == TRANSITION_TO_STABLE) {
132                transitionTo(mStableState);
133            } else {
134                dispatchMessage(msg);
135            }
136            return true;
137        }
138
139        private void dispatchMessage(Message msg) {
140            BluetoothDeviceProfileState deviceProfileMgr =
141              (BluetoothDeviceProfileState)msg.obj;
142            int cmd = msg.arg1;
143            if (mPendingDevice == null || mPendingDevice.equals(deviceProfileMgr.getDevice())) {
144                mPendingDevice = deviceProfileMgr.getDevice();
145                deviceProfileMgr.sendMessage(cmd);
146            } else {
147                Message deferMsg = new Message();
148                deferMsg.arg1 = cmd;
149                deferMsg.obj = deviceProfileMgr;
150                deferMessage(deferMsg);
151            }
152        }
153    }
154
155    private void log(String message) {
156        if (DBG) {
157            Log.i(TAG, "Message:" + message);
158        }
159    }
160}
161