Last active
October 7, 2021 15:42
-
-
Save joinAero/64e68c9d63987284f7f0 to your computer and use it in GitHub Desktop.
Android - The bluetooth listener and profile proxy.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cc.cubone.turbo.core.bluetooth; | |
/** | |
* Interface definition for a callback to be invoked when bluetooth state changed. | |
*/ | |
public interface BluetoothCallback { | |
/** | |
* Called when the bluetooth is off. | |
*/ | |
void onBluetoothOff(); | |
/** | |
* Called when the bluetooth is turning on. | |
*/ | |
void onBluetoothTurningOn(); | |
/** | |
* Called when the bluetooth is on, and ready for use. | |
*/ | |
void onBluetoothOn(); | |
/** | |
* Called when the bluetooth is turning off. | |
*/ | |
void onBluetoothTurningOff(); | |
/** | |
* This stub class provides empty implementations of the methods. | |
*/ | |
public static class Stub implements BluetoothCallback { | |
@Override | |
public void onBluetoothOff() { | |
} | |
@Override | |
public void onBluetoothTurningOn() { | |
} | |
@Override | |
public void onBluetoothOn() { | |
} | |
@Override | |
public void onBluetoothTurningOff() { | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cc.cubone.turbo.core.bluetooth; | |
import android.bluetooth.BluetoothAdapter; | |
import android.content.BroadcastReceiver; | |
import android.content.Context; | |
import android.content.Intent; | |
import android.content.IntentFilter; | |
/** | |
* The bluetooth listener for receiving the state changed. | |
* | |
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. | |
*/ | |
public class BluetoothListener { | |
private Context mContext; | |
private BluetoothCallback mCallback; | |
private BluetoothReceiver mReceiver; | |
public BluetoothListener(Context context) { | |
mContext = context; | |
} | |
/** | |
* Register this bluetooth listener to the context with a callback. | |
*/ | |
public void register(BluetoothCallback callback) { | |
mCallback = callback; | |
if (mReceiver == null) { | |
mReceiver = new BluetoothReceiver(); | |
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED); | |
mContext.registerReceiver(mReceiver, filter); | |
} | |
} | |
/** | |
* Unregister this bluetooth listener from the context. | |
*/ | |
public void unregister() { | |
if (mReceiver != null) { | |
mContext.unregisterReceiver(mReceiver); | |
mReceiver = null; | |
} | |
} | |
/** | |
* Receives the {@link android.bluetooth.BluetoothAdapter#ACTION_STATE_CHANGED}. | |
*/ | |
private class BluetoothReceiver extends BroadcastReceiver { | |
@Override | |
public void onReceive(Context context, Intent intent) { | |
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, | |
BluetoothAdapter.ERROR); | |
switch (state) { | |
case BluetoothAdapter.STATE_OFF: | |
mCallback.onBluetoothOff(); | |
break; | |
case BluetoothAdapter.STATE_TURNING_ON: | |
mCallback.onBluetoothTurningOn(); | |
break; | |
case BluetoothAdapter.STATE_ON: | |
mCallback.onBluetoothOn(); | |
break; | |
case BluetoothAdapter.STATE_TURNING_OFF: | |
mCallback.onBluetoothTurningOff(); | |
break; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cc.cubone.turbo.core.bluetooth; | |
import android.Manifest; | |
import android.bluetooth.BluetoothAdapter; | |
import android.bluetooth.BluetoothDevice; | |
import android.bluetooth.BluetoothProfile; | |
import android.content.Context; | |
import android.support.annotation.RequiresPermission; | |
import android.util.Log; | |
import java.lang.reflect.InvocationTargetException; | |
import java.lang.reflect.Method; | |
import java.util.List; | |
import java.util.Set; | |
/** | |
* The bluetooth profile proxy for operating the system bluetooth profile. | |
* | |
* @see android.bluetooth.BluetoothProfile | |
* @see android.bluetooth.BluetoothAdapter | |
* @see android.bluetooth.BluetoothManager | |
*/ | |
public class BluetoothProfileProxy implements BluetoothProfile, BluetoothProfile.ServiceListener { | |
private static final String TAG = "BluetoothProfileProxy"; | |
/** The profile is in unknown state */ | |
public static final int STATE_UNKNOWN = -1; | |
/** Input Device Profile */ | |
public static final int INPUT_DEVICE = 4; | |
private Context mContext; | |
private BluetoothAdapter mBluetoothAdapter; | |
private BluetoothProfile mBluetoothProfile; | |
private BluetoothListener mBluetoothListener; | |
private BluetoothCallback mBluetoothCallback; | |
private int mProfile = -1; | |
private boolean mRegistered = false; | |
public BluetoothProfileProxy(Context context) { | |
mContext = context; | |
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); | |
} | |
/** | |
* Register the proxy for {@link #INPUT_DEVICE} profile. | |
* | |
* @see #register(int, boolean) | |
*/ | |
public boolean register(boolean followBluetooth) { | |
return register(INPUT_DEVICE, followBluetooth); | |
} | |
/** | |
* Register the proxy for a bluetooth profile. | |
* | |
* @param profile The Bluetooth profile; either {@link #HEALTH}, {@link #HEADSET}, | |
* {@link #A2DP}, {@link #GATT} or {@link #GATT_SERVER}. | |
* @param followBluetooth Whether follow bluetooth state changed event or not. If follow, it | |
* will auto register when the bluetooth turn on, unregister when turn | |
* off. | |
* | |
* @see BluetoothAdapter#getProfileProxy(Context, ServiceListener, int) | |
*/ | |
public boolean register(int profile, boolean followBluetooth) { | |
if (mBluetoothAdapter == null) { | |
Log.w(TAG, "Bluetooth is not supported"); | |
return false; | |
} | |
if (mRegistered) { | |
Log.w(TAG, "BluetoothProfile has registered"); | |
return false; | |
} | |
mRegistered = mBluetoothAdapter.getProfileProxy(mContext, this, profile); | |
if (mRegistered) { | |
mProfile = profile; | |
} | |
if (followBluetooth) { | |
mProfile = profile; | |
followBluetooth(); | |
} | |
return mRegistered; | |
} | |
/** | |
* Unregister the proxy. | |
*/ | |
public void unregister() { | |
unregister(true); | |
} | |
private void unregister(boolean formUser) { | |
if (!mRegistered) return; | |
mBluetoothAdapter.closeProfileProxy(mProfile, mBluetoothProfile); | |
mBluetoothProfile = null; | |
mRegistered = false; | |
if (formUser) { | |
unfollowBluetooth(); | |
mProfile = -1; | |
} | |
} | |
/** | |
* Enable follow bluetooth state changed event. | |
* | |
* <p>If follow, it will auto register when the bluetooth turn on, unregister when turn off. | |
* | |
* <p>Could {@link #setBluetoothCallback(BluetoothCallback)} to listen the bluetooth state | |
* changed event. | |
*/ | |
public void followBluetooth() { | |
if (mBluetoothListener != null) return; | |
mBluetoothListener = new BluetoothListener(mContext); | |
mBluetoothListener.register(new BluetoothCallback() { | |
@Override | |
public void onBluetoothOff() { | |
unregister(false); | |
if (mBluetoothCallback != null) mBluetoothCallback.onBluetoothOff(); | |
} | |
@Override | |
public void onBluetoothTurningOn() { | |
if (mBluetoothCallback != null) mBluetoothCallback.onBluetoothTurningOn(); | |
} | |
@Override | |
public void onBluetoothOn() { | |
register(mProfile, false); | |
if (mBluetoothCallback != null) mBluetoothCallback.onBluetoothOn(); | |
} | |
@Override | |
public void onBluetoothTurningOff() { | |
if (mBluetoothCallback != null) mBluetoothCallback.onBluetoothTurningOff(); | |
} | |
}); | |
} | |
/** | |
* Disable follow bluetooth state changed event. | |
*/ | |
public void unfollowBluetooth() { | |
if (mBluetoothListener == null) return; | |
mBluetoothListener.unregister(); | |
mBluetoothListener = null; | |
} | |
/** | |
* Set bluetooth callback to listen the bluetooth state changed event. | |
* | |
* <p>It only make effect when enable follow bluetooth. | |
*/ | |
public void setBluetoothCallback(BluetoothCallback callback) { | |
mBluetoothCallback = callback; | |
} | |
/** | |
* Whether registered or not. | |
*/ | |
public boolean isRegistered() { | |
return mRegistered; | |
} | |
/** | |
* Get the bluetooth profile. | |
*/ | |
public BluetoothProfile getBluetoothProfile() { | |
return mBluetoothProfile; | |
} | |
/** | |
* Whether the bluetooth profile is got or not. | |
*/ | |
public boolean hasBluetoothProfile() { | |
return mBluetoothProfile != null; | |
} | |
private boolean checkNotBluetoothProfile() { | |
if (mBluetoothProfile == null) { | |
Log.w(TAG, "BluetoothProfile not connected"); | |
return true; | |
} | |
return false; | |
} | |
private boolean checkNotBluetoothDevice(BluetoothDevice device) { | |
if (device == null) { | |
Log.w(TAG, "BluetoothDevice is null"); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Get connected devices for this specific profile. | |
* | |
* @see BluetoothProfile#getConnectedDevices() | |
*/ | |
public List<BluetoothDevice> getConnectedDevices() { | |
if (checkNotBluetoothProfile()) return null; | |
return mBluetoothProfile.getConnectedDevices(); | |
} | |
/** | |
* Get a list of devices that match any of the given connection states. | |
* | |
* @see BluetoothProfile#getDevicesMatchingConnectionStates(int[]) | |
*/ | |
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { | |
if (checkNotBluetoothProfile()) return null; | |
return mBluetoothProfile.getDevicesMatchingConnectionStates(states); | |
} | |
/** | |
* Get the current connection state of the profile. | |
* | |
* @see BluetoothProfile#getConnectionState(BluetoothDevice) | |
*/ | |
public int getConnectionState(BluetoothDevice device) { | |
if (checkNotBluetoothProfile() || checkNotBluetoothDevice(device)) return STATE_UNKNOWN; | |
return mBluetoothProfile.getConnectionState(device); | |
} | |
/** | |
* Get the set of {@link BluetoothDevice} that are bonded (paired). | |
* | |
* <p>The bonded device may be connected, connecting or disconnect, however bonded at present. | |
* | |
* <p>Requires {@link android.Manifest.permission#BLUETOOTH}. | |
*/ | |
@RequiresPermission(Manifest.permission.BLUETOOTH) | |
public Set<BluetoothDevice> getBondedDevices() { | |
if (mBluetoothAdapter == null) { | |
Log.w(TAG, "Bluetooth is not supported"); | |
return null; | |
} | |
return mBluetoothAdapter.getBondedDevices(); | |
} | |
/** | |
* Connect the bluetooth device. | |
*/ | |
public boolean connect(BluetoothDevice device) { | |
if (checkNotBluetoothDevice(device)) return false; | |
return invoke(mBluetoothProfile, "connect", device); | |
} | |
/** | |
* Disconnect the bluetooth device. | |
*/ | |
public boolean disconnect(BluetoothDevice device) { | |
if (checkNotBluetoothDevice(device)) return false; | |
return invoke(mBluetoothProfile, "disconnect", device); | |
} | |
/** | |
* Create bond for the bluetooth device. | |
*/ | |
public boolean createBond(BluetoothDevice device) { | |
if (checkNotBluetoothDevice(device)) return false; | |
return invoke(device, "createBond"); | |
} | |
/** | |
* Remove bond for the bluetooth device. | |
*/ | |
public boolean removeBond(BluetoothDevice device) { | |
if (checkNotBluetoothDevice(device)) return false; | |
return invoke(device, "removeBond"); | |
} | |
private boolean invoke(Object obj, String method, Object... args) { | |
if (obj == null) return false; | |
if (method == null || method.isEmpty()) return false; | |
boolean ok = false; | |
try { | |
Class<?>[] paramTypes = null; | |
if (args != null) { | |
int len = args.length; | |
paramTypes = new Class<?>[len]; | |
for (int i = 0; i < len; ++i) { | |
if (args[i] == null) { | |
throw new NullPointerException(); | |
} | |
paramTypes[i] = args[i].getClass(); | |
} | |
} | |
Class<?> cls = obj.getClass(); | |
Method func = cls.getMethod(method, paramTypes); | |
func.setAccessible(true); | |
Object result = func.invoke(obj, args); | |
try { | |
ok = (Boolean) result; | |
} catch (ClassCastException e) { | |
ok = true; | |
} | |
} catch (NoSuchMethodException e) { | |
e.printStackTrace(); | |
} catch (IllegalAccessException e) { | |
e.printStackTrace(); | |
} catch (InvocationTargetException e) { | |
e.printStackTrace(); | |
} | |
return ok; | |
} | |
@Override | |
public void onServiceConnected(int profile, BluetoothProfile proxy) { | |
mBluetoothProfile = proxy; | |
} | |
@Override | |
public void onServiceDisconnected(int profile) { | |
mBluetoothProfile = null; | |
} | |
@Override | |
protected void finalize() throws Throwable { | |
try { | |
unregister(true); | |
} finally { | |
super.finalize(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cc.cubone.turbo.core.bluetooth; | |
import android.bluetooth.BluetoothAdapter; | |
import android.content.Context; | |
import android.os.Build; | |
import static android.content.pm.PackageManager.FEATURE_BLUETOOTH_LE; | |
/** | |
* Utility for detecting the bluetooth state. | |
*/ | |
public class BluetoothUtils { | |
/** | |
* Whether bluetooth is supported on the device. | |
*/ | |
public static boolean bluetoothAvailable() { | |
return BluetoothAdapter.getDefaultAdapter() != null; | |
} | |
/** | |
* Whether BLE is supported on the device. | |
*/ | |
public static boolean bluetoothLeAvailable(Context context) { | |
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 // >= 18 | |
&& context.getPackageManager().hasSystemFeature(FEATURE_BLUETOOTH_LE); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment