package com.blakequ.bluetooth_manager_lib.connect.multiple; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.content.Context; import android.os.Looper; import android.os.SystemClock; import com.blakequ.bluetooth_manager_lib.BleManager; import com.blakequ.bluetooth_manager_lib.BleParamsOptions; import com.blakequ.bluetooth_manager_lib.connect.BluetoothConnectInterface; import com.blakequ.bluetooth_manager_lib.connect.ConnectConfig; import com.blakequ.bluetooth_manager_lib.connect.ConnectState; import com.blakequ.bluetooth_manager_lib.connect.ConnectStateListener; import com.blakequ.bluetooth_manager_lib.connect.ReconnectParamsBean; import com.blakequ.bluetooth_manager_lib.util.BluetoothUtils; import com.blankj.utilcode.util.LogUtils; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; /** * Copyright (C) BlakeQu All Rights Reserved *

* Licensed under the blakequ.com License, Version 1.0 (the "License"); * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. *

* author : quhao
* date : 2016/8/19 9:48
* last modify author :
* version : 1.0
* description:the queue using for manager connect request */ public abstract class ConnectRequestQueue extends BluetoothConnectInterface{ private static final String TAG = "ConnectRequestQueue"; private Map reconnectMap; //reconnect device list and reconnect times number private Map macMap;// private Map gattMap;//notice:ArrayMap is not support concurrent, so can not use ArrayMap private Queue deviceQueue; private final BluetoothUtils mBluetoothUtils; private List connectStateListeners; public ConnectRequestQueue(Context context){ super(context); macMap = new ConcurrentHashMap();//if not consider concurrent, should use ArrayMap gattMap = new ConcurrentHashMap(); reconnectMap = new ConcurrentHashMap(); deviceQueue = new ConcurrentLinkedQueue<>(); mBluetoothUtils = BluetoothUtils.getInstance(context); connectStateListeners = new ArrayList<>(); } public void addConnectStateListener(ConnectStateListener listener){ synchronized (connectStateListeners){ connectStateListeners.add(listener); } } public void removeConnectStateListener(ConnectStateListener listener){ synchronized (connectStateListeners){ connectStateListeners.remove(listener); } } @Override protected void onDeviceConnected(BluetoothGatt gatt) { if (gatt != null){ updateConnectState(gatt.getDevice().getAddress(), ConnectState.CONNECTED); } } @Override protected void onDeviceDisconnect(BluetoothGatt gatt, int errorState) { LogUtils.e( "Disconnected from GATT server address:" + gatt.getDevice().getAddress()); //可以不关闭,以便重用,因为在连接connect的时候可以快速连接 if (!mBluetoothUtils.isBluetoothIsEnable()){ //关闭所有的设备 closeAll(); }else { close(gatt.getDevice().getAddress());//防止出现status 133 } } @Override protected void onDiscoverServicesFail(BluetoothGatt gatt) { if (gatt != null){ updateConnectState(gatt.getDevice().getAddress(), ConnectState.NORMAL); } } @Override protected void onDiscoverServicesSuccess(BluetoothGatt gatt){ if (gatt != null){ updateConnectState(gatt.getDevice().getAddress(), ConnectState.CONNECTED); } } /** * start connect device one by one */ public void startConnect(){ if (deviceQueue.size() > 0 && mBluetoothUtils.isBluetoothIsEnable()){ triggerConnectNextDevice(); }else { triggerReconnect(""); LogUtils.e( "startConnect--Fail to from connect queue, and start reconnect task. ble state:" + mBluetoothUtils.isBluetoothIsEnable()); } } /** * start connect device(will trigger reconnect) * @param macAddress */ public void startConnect(String macAddress){ if (macAddress != null && macAddress.length() > 0){ if (macMap.containsKey(macAddress)){ ConnectState state = macMap.get(macAddress); //如果是未连接状态,则开启重连,重置重连次数,并立即连接 if (macMap.get(macAddress) == ConnectState.NORMAL){ ReconnectParamsBean bean; if (!reconnectMap.containsKey(macAddress)){ bean = new ReconnectParamsBean(macAddress); reconnectMap.put(macAddress, bean); }else{ bean = reconnectMap.get(macAddress); } bean.setReconnectNow(true); startReconnectTask(); }else{ LogUtils.i( "Device is " + state + " state"); } }else{ LogUtils.e( "Fail to connect device, device can not found in queue, you must invoke addDeviceToQueue(Stirng)"); } }else{ LogUtils.e( "Fail to connect device, mac address is null"); } } /** * connect bluetooth device one by one * @return the next connect device */ private void triggerConnectNextDevice(){ String mac = deviceQueue.peek(); if (!isEmpty(mac)){ LogUtils.i( "Start trigger connect device "+mac); connect(mac); } } private void updateConnectState(String address, ConnectState state) { //bug:Can not remove device from queue, this position just update connect state if (macMap.containsKey(address)) { macMap.put(address, state); updateConnectStateListener(address, state); } switch (state){ case NORMAL: //disconnect or close String mac = deviceQueue.peek(); if (!isEmpty(mac)){ if (address.equals(mac)){ deviceQueue.poll(); } triggerConnectNextDevice(); } triggerReconnect(address); break; case CONNECTED: reconnectMap.remove(address); String mac1 = deviceQueue.peek(); if (!isEmpty(mac1)){ if (address.equals(mac1)){ deviceQueue.poll(); } triggerConnectNextDevice(); } triggerReconnect(address); break; case CONNECTING: //start check time out connect BleParamsOptions options = BleManager.getBleParamsOptions(); getMainLooperHandler().postDelayed(timeOutTask, options.getConnectTimeOutTimes()); break; } } /** * connect time out task */ private Runnable timeOutTask = new Runnable() { @Override public void run() { if (!mBluetoothUtils.isBluetoothIsEnable()){ LogUtils.w( "Fail to connect device! Bluetooth is not enable!"); closeAll(); } } }; /** * release resource */ @Override public void release(){ macMap.clear(); closeAll(); gattMap.clear(); reconnectMap.clear(); deviceQueue.clear(); getMainLooperHandler().removeCallbacks(reconnectTask); } /** * get the size of current queue *

Notice:this len maybe is not equal of maxLen(connect device num<=maxLen), is dynamic length by sensor physical truth * @see #getMaxLen() */ public int getQueueSize(){ return macMap.size(); } /** * add device to connect queue, if the number out of range will discard. * @param macAddress * @see #startConnect() */ public void addDeviceToQueue(String macAddress){ if (!macMap.containsKey(macAddress)){ if (macMap.size() >= getMaxLen()){ String address = deviceQueue.poll(); if (isEmpty(address)){ address = getFirstDevice(); } removeDeviceFromQueue(address); } deviceQueue.add(macAddress); macMap.put(macAddress, ConnectState.NORMAL); } } /** * add device to connect queue * @param devices * @see #startConnect() */ public void addDeviceToQueue(String[] devices){ if (devices != null && devices.length > 0){ for (int i=0; i getAllDevice(){ if (macMap.size() <= 0) return Collections.EMPTY_LIST; List list = new ArrayList<>(); for (String key:macMap.keySet()){ list.add(key); } return list; } private String getFirstDevice(){ if (macMap.size() <= 0) return null; for (String key:macMap.keySet()){ return key; } return null; } /** * get all connected device * @return */ public List getAllConnectedDevice(){ if (macMap.size() <= 0) return Collections.EMPTY_LIST; List list = new ArrayList<>(); for (String key:macMap.keySet()){ if (macMap.get(key) == ConnectState.CONNECTED){ list.add(key); } } return list; } /** * is contain device * @param address * @return */ public boolean containsDevice(String address){ return macMap.containsKey(address); } /** * get bluetooth state of connect * @param address * @return */ public ConnectState getDeviceState(String address){ return macMap.get(address); } public List getAllConnectingDevice(){ if (macMap.size() <= 0) return Collections.EMPTY_LIST; List list = new ArrayList<>(); for (String key:macMap.keySet()){ if (macMap.get(key) == ConnectState.CONNECTING){ list.add(key); } } return list; } /** * has device is not connected * @return */ public boolean isDisconnectDevice(){ for (ConnectState value:macMap.values()) { if (value == ConnectState.NORMAL){ return true; } } return false; } /** * is have device is connecting * @return */ public boolean isConnectingDevice(){ for (ConnectState value:macMap.values()){ if (value == ConnectState.CONNECTING){ return true; } } return false; } /** * is have device is connected * @return */ public boolean isConnectedDevice(){ for (ConnectState value:macMap.values()){ if (value == ConnectState.CONNECTED){ return true; } } return false; } /** * 获取连接设备的BluetoothGatt对象,如果没有返回null * @param address * @return */ public BluetoothGatt getBluetoothGatt(String address){ if (!isEmpty(address) && gattMap.containsKey(address)){ return gattMap.get(address); } return null; } /** * trigger reconnect task */ private void triggerReconnect(String mac){ //if deviceQueue is null, start reconnect if (deviceQueue.size() == 0){ //将重连的设备全部放入重连队列 for (String key:macMap.keySet()){ if (macMap.get(key) == ConnectState.NORMAL){ ReconnectParamsBean bean; if (!reconnectMap.containsKey(key)){ bean = new ReconnectParamsBean(key); reconnectMap.put(key, bean); }else if(key.equals(mac)){ bean = reconnectMap.get(key); bean.addNumber(); LogUtils.d( "trigger reconnect, reconnect after "+(bean.getNextReconnectTime() - SystemClock.elapsedRealtime())/1000+" seconds"); } } } startReconnectTask(); } } /** * can not reconnect all the time */ private synchronized void startReconnectTask(){ if (reconnectMap.size() <= 0) return; long nextTime = SystemClock.elapsedRealtime()*2; String address = ""; //select minimum time of list for (String addr:reconnectMap.keySet()){ ReconnectParamsBean bean = reconnectMap.get(addr); if (bean.getNextReconnectTime() < nextTime){ nextTime = bean.getNextReconnectTime(); address = addr; } } //start reconnect task if (!isEmpty(address)){ if (nextTime <= SystemClock.elapsedRealtime()){ LogUtils.d( "start reconnect device:"+address); reconnectDevice(address); }else{ LogUtils.d( "start reconnect device "+address+" after "+(nextTime - SystemClock.elapsedRealtime())/1000+" seconds"); getMainLooperHandler().removeCallbacks(reconnectTask); getMainLooperHandler().postDelayed(reconnectTask, nextTime - SystemClock.elapsedRealtime()); } } } /** * reconnect runnable */ private Runnable reconnectTask = new Runnable() { @Override public void run() { LogUtils.d( "Start reconnect task by handler"); startReconnectTask(); } }; /** * reconnect device * @param address */ private synchronized void reconnectDevice(final String address){ if (macMap.containsKey(address)){ ReconnectParamsBean bean = reconnectMap.get(address); if (mBluetoothUtils.isBluetoothIsEnable()) { if (bean == null){ reconnectMap.put(address, new ReconnectParamsBean(address)); } //check is connected or connectting ConnectState state = macMap.get(address); if (state == ConnectState.NORMAL){ LogUtils.d( "Start reconnect device "+address+" reconnect number is "+bean.getNumber()); runOnUiThread(new Runnable() { @Override public void run() { connect(address); } }); }else{ LogUtils.w( "Fail to reconnect device! "+address+" state is "+state); } }else { closeAll(); LogUtils.w( "Fail to reconnect device! Bluetooth is not enable!"); } }else{ LogUtils.w("Fail to reconnect device! "+address+" is remove from reconnectMap"); reconnectMap.remove(address); } } /** * You should invoke {@link #startConnect()} to begin to connect device. Not recommended for direct use this method * @see #startConnect() * @param address * @return */ protected boolean connect(final String address) { BluetoothAdapter mAdapter = mBluetoothUtils.getBluetoothAdapter(); if (mAdapter == null || address == null) { LogUtils.e("BluetoothAdapter not initialized or unspecified address "+address); updateConnectStateListener(address, ConnectState.NORMAL); return false; } if (!mBluetoothUtils.isBluetoothIsEnable()){ LogUtils.e("bluetooth is not enable."); closeAll(); // updateConnectStateListener(address, ConnectState.NORMAL); return false; } if (isEmpty(getServiceUUID())){ LogUtils.w("Service uuid is null"); } // Previously connected device. Try to reconnect. if (gattMap.containsKey(address)){ BluetoothGatt mBluetoothGatt = gattMap.get(address); LogUtils.i("Trying to use an existing gatt and reconnection device " + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); if (mBluetoothGatt.connect()) { updateConnectState(address, ConnectState.CONNECTING); return true; } else { close(address); return false; } } BluetoothDevice device = mAdapter.getRemoteDevice(address); if (device != null){ /*if We want to directly connect to the device, we can setting the autoConnect parameter to false.*/ BluetoothGatt mBluetoothGatt = device.connectGatt(context, false, gattCallback); if (mBluetoothGatt != null){ LogUtils.i("create a new connection address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); gattMap.put(address, mBluetoothGatt); updateConnectState(address, ConnectState.CONNECTING); return true; } else { LogUtils.e("Get Gatt fail!, address=" + address + " thread:" + (Thread.currentThread() == Looper.getMainLooper().getThread())); } } else { LogUtils.e("Device not found, address=" + address); } return false; } /** * 关闭蓝牙连接,会释放BluetoothGatt持有的所有资源 * @param address */ public boolean close(String address) { if (!isEmpty(address) && gattMap.containsKey(address)){ LogUtils.w( "close gatt server " + address); BluetoothGatt mBluetoothGatt = gattMap.get(address); mBluetoothGatt.close(); gattMap.remove(address); updateConnectState(address, ConnectState.NORMAL); return true; } return false; } /** * 关闭所有蓝牙设备 */ public void closeAll(){ for (String address:gattMap.keySet()) { close(address); } } /** * 断开蓝牙连接,不会释放BluetoothGatt持有的所有资源,可以调用mBluetoothGatt.connect()很快重新连接上 * 如果不及时释放资源,可能出现133错误,http://www.loverobots.cn/android-ble-connection-solution-bluetoothgatt-status-133.html * @param address */ public void disconnect(String address){ if (!isEmpty(address) && gattMap.containsKey(address)){ LogUtils.w("disconnect gatt server " + address); BluetoothGatt mBluetoothGatt = gattMap.get(address); mBluetoothGatt.disconnect(); updateConnectState(address, ConnectState.NORMAL); } } private void updateConnectStateListener(String address, ConnectState state){ synchronized (connectStateListeners){ for (ConnectStateListener listener:connectStateListeners){ if (listener != null) listener.onConnectStateChanged(address, state); } } } /** * max connected number of bluetooth queue * @return */ public int getMaxLen(){ return ConnectConfig.maxConnectDeviceNum; } public boolean isEmpty(String str) { return str == null || str.length() == 0; } }