1/*
2 * Copyright (C) 2017 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 com.android.car;
17
18import android.os.Message;
19import android.util.Log;
20
21import com.android.internal.util.State;
22import com.android.internal.util.StateMachine;
23
24import java.io.PrintWriter;
25
26/**
27 * BluetoothAutoConnectStateMachine is a simple state machine to manage automatic bluetooth
28 * connection attempts.  It has 2 states Idle & Processing.
29 * Idle is the starting state. Incoming 'CONNECT' message is honored and connection attempts are
30 * triggered.  A Connection Timeout is also set before transitioning to Processing State
31 * Processing state ignores any incoming 'CONNECT' requests from any of the vehicle signals,
32 * since it is already in the middle of a connection attempt.  Processing moves back to Idle, when
33 * either
34 * 1. All the connections are made.
35 * 2. All connection attempts failed and there is nothing else to try.
36 */
37public class BluetoothAutoConnectStateMachine extends StateMachine {
38    private static final String TAG = "BTAutoConnStateMachine";
39    private static final boolean DBG = false;
40    private final BluetoothDeviceConnectionPolicy mPolicy;
41    private final Idle mIdle;
42    private final Processing mProcessing;
43    // The messages handled by the States in the State Machine
44    public static final int CONNECT = 101;
45    public static final int DISCONNECT = 102;
46    public static final int CONNECT_TIMEOUT = 103;
47    public static final int DEVICE_CONNECTED = 104;
48    public static final int DEVICE_DISCONNECTED = 105;
49    public static final int CONNECTION_TIMEOUT_MS = 8000;
50
51
52    BluetoothAutoConnectStateMachine(BluetoothDeviceConnectionPolicy policy) {
53        super(TAG);
54        mPolicy = policy;
55
56        // Two supported states -
57        // Idle when ready to accept connections
58        // Processing when in the middle of a connection attempt.
59        mIdle = new Idle();
60        mProcessing = new Processing();
61
62        addState(mIdle);
63        addState(mProcessing);
64        setInitialState(mIdle);
65    }
66
67    public static BluetoothAutoConnectStateMachine make(BluetoothDeviceConnectionPolicy policy) {
68        BluetoothAutoConnectStateMachine mStateMachine = new BluetoothAutoConnectStateMachine(
69                policy);
70        mStateMachine.start();
71        return mStateMachine;
72    }
73
74    public void doQuit() {
75        quitNow();
76    }
77
78    /**
79     * Idle State is the Initial State, when the system is accepting incoming 'CONNECT' requests.
80     * Attempts a connection whenever the state transitions into Idle.
81     * If the policy finds a device to connect on a profile, transitions to Processing.
82     * If there is nothing to connect to, wait for the next 'CONNECT' message to try next.
83     */
84    private class Idle extends State {
85        @Override
86        public void enter() {
87            if (DBG) {
88                Log.d(TAG, "Enter Idle");
89            }
90            connectToBluetoothDevice();
91        }
92
93        @Override
94        public boolean processMessage(Message msg) {
95            if (DBG) {
96                Log.d(TAG, "Idle processMessage " + msg.what);
97            }
98            switch (msg.what) {
99                case CONNECT: {
100                    if (DBG) {
101                        Log.d(TAG, "Idle->Connect:");
102                    }
103                    connectToBluetoothDevice();
104                    break;
105                }
106
107                case DEVICE_CONNECTED: {
108                    if (DBG) {
109                        Log.d(TAG, "Idle->DeviceConnected: Ignored");
110                    }
111                    break;
112                }
113
114                default: {
115                    if (DBG) {
116                        Log.d(TAG, "Idle->Unhandled Msg; " + msg.what);
117                    }
118                    return false;
119                }
120            }
121            return true;
122        }
123
124        /**
125         * Instruct the policy to find and connect to a device on a connectable profile.
126         * If the policy reports that there is nothing to connect to, stay in the Idle state.
127         * If it found a {device, profile} combination to attempt a connection, move to
128         * Processing state
129         */
130        private void connectToBluetoothDevice() {
131            boolean deviceToConnectFound = mPolicy.findDeviceToConnect();
132            if (deviceToConnectFound) {
133                transitionTo(mProcessing);
134            } else {
135                // Stay in Idle State and wait for the next 'CONNECT' message.
136                if (DBG) {
137                    Log.d(TAG, "Idle->No device to connect");
138                }
139            }
140        }
141
142        @Override
143        public void exit() {
144            if (DBG) {
145                Log.d(TAG, "Exit Idle");
146            }
147        }
148
149    }
150
151    /**
152     * Processing state indicates the system is processing a auto connect trigger and will ignore
153     * connection requests.
154     */
155    private class Processing extends State {
156        @Override
157        public void enter() {
158            if (DBG) {
159                Log.d(TAG, "Enter Processing");
160            }
161
162        }
163
164        @Override
165        public boolean processMessage(Message msg) {
166            if (DBG) {
167                Log.d(TAG, "Processing processMessage " + msg.what);
168            }
169            BluetoothDeviceConnectionPolicy.ConnectionParams params;
170            switch (msg.what) {
171                case CONNECT_TIMEOUT: {
172                    if (DBG) {
173                        Log.d(TAG, "Connection Timeout");
174                    }
175                    params = (BluetoothDeviceConnectionPolicy.ConnectionParams) msg.obj;
176                    mPolicy.updateDeviceConnectionStatus(params, false);
177                    transitionTo(mIdle);
178                    break;
179                }
180
181                case DEVICE_CONNECTED:
182                    // fall through
183                case DEVICE_DISCONNECTED: {
184                    removeMessages(CONNECT_TIMEOUT);
185                    transitionTo(mIdle);
186                    break;
187                }
188
189                default:
190                    if (DBG) {
191                        Log.d(TAG, "Processing->Unhandled Msg: " + msg.what);
192                    }
193                    return false;
194            }
195            return true;
196        }
197
198        @Override
199        public void exit() {
200            if (DBG) {
201                Log.d(TAG, "Exit Processing");
202            }
203        }
204    }
205
206    public void dump(PrintWriter writer) {
207        writer.println("StateMachine: " + this.toString());
208    }
209
210}
211