Skip to content

Instantly share code, notes, and snippets.

@joinAero
Last active October 7, 2021 15:42
Show Gist options
  • Save joinAero/64e68c9d63987284f7f0 to your computer and use it in GitHub Desktop.
Save joinAero/64e68c9d63987284f7f0 to your computer and use it in GitHub Desktop.
Android - The bluetooth listener and profile proxy.
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() {
}
}
}
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;
}
}
}
}
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();
}
}
}
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