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