Java tutorial
/* * Copyright 2015 Hello Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * 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. */ package is.hello.buruberi.bluetooth.stacks.android; import android.Manifest; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RequiresPermission; import android.support.annotation.VisibleForTesting; import android.support.v4.content.LocalBroadcastManager; import java.util.List; import is.hello.buruberi.bluetooth.errors.ChangePowerStateException; import is.hello.buruberi.bluetooth.errors.UserDisabledBuruberiException; import is.hello.buruberi.bluetooth.stacks.BluetoothStack; import is.hello.buruberi.bluetooth.stacks.GattPeripheral; import is.hello.buruberi.bluetooth.stacks.util.AdvertisingData; import is.hello.buruberi.bluetooth.stacks.util.ErrorListener; import is.hello.buruberi.bluetooth.stacks.util.LoggerFacade; import is.hello.buruberi.bluetooth.stacks.util.PeripheralCriteria; import is.hello.buruberi.util.Rx; import rx.Observable; import rx.Scheduler; import rx.functions.Func1; import rx.subjects.ReplaySubject; public class NativeBluetoothStack implements BluetoothStack { private static final String SAVED_DEVICE = NativeBluetoothStack.class.getName() + "#SAVED_DEVICE"; private static final String SAVED_RSSI = NativeBluetoothStack.class.getName() + "#SAVED_RSSI"; private static final String SAVED_ADVERTISING_DATA = NativeBluetoothStack.class.getName() + "#SAVED_ADVERTISING_DATA"; /*package*/ final @NonNull Context applicationContext; private final @NonNull ErrorListener errorListener; private final @NonNull LoggerFacade logger; private final @NonNull Scheduler scheduler = Rx.mainThreadScheduler(); /*package*/ final @NonNull BluetoothManager bluetoothManager; private final @Nullable BluetoothAdapter adapter; private final @NonNull ReplaySubject<Boolean> enabled = ReplaySubject.createWithSize(1); @RequiresPermission(Manifest.permission.BLUETOOTH) public NativeBluetoothStack(@NonNull Context applicationContext, @NonNull ErrorListener errorListener, @NonNull LoggerFacade logger) { this.applicationContext = applicationContext; this.errorListener = errorListener; this.logger = logger; this.bluetoothManager = (BluetoothManager) applicationContext.getSystemService(Context.BLUETOOTH_SERVICE); this.adapter = bluetoothManager.getAdapter(); if (adapter != null) { final BroadcastReceiver powerStateReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (newState == BluetoothAdapter.STATE_ON) { enabled.onNext(true); } else if (newState == BluetoothAdapter.STATE_OFF || newState == BluetoothAdapter.ERROR) { enabled.onNext(false); } } }; applicationContext.registerReceiver(powerStateReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); enabled.onNext(adapter.isEnabled()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { final BroadcastReceiver pairingReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); final Intent broadcast = new Intent(ACTION_PAIRING_REQUEST); broadcast.putExtra(GattPeripheral.EXTRA_NAME, device.getName()); broadcast.putExtra(GattPeripheral.EXTRA_ADDRESS, device.getAddress()); LocalBroadcastManager.getInstance(context).sendBroadcast(broadcast); } }; applicationContext.registerReceiver(pairingReceiver, new IntentFilter(BluetoothDevice.ACTION_PAIRING_REQUEST)); } } else { logger.warn(LOG_TAG, "Host device has no bluetooth hardware!"); enabled.onNext(false); } } @NonNull BluetoothAdapter getAdapter() { if (adapter == null) { throw new NullPointerException("Host device has no bluetooth hardware!"); } return adapter; } @VisibleForTesting LePeripheralScanner createLeScanner(@NonNull PeripheralCriteria peripheralCriteria) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { return new LollipopLePeripheralScanner(this, peripheralCriteria); } else { return new LegacyLePeripheralScanner(this, peripheralCriteria); } } @NonNull @Override @RequiresPermission(allOf = { Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, }) public Observable<List<GattPeripheral>> discoverPeripherals( final @NonNull PeripheralCriteria peripheralCriteria) { if (adapter != null && adapter.isEnabled()) { if (peripheralCriteria.wantsHighPowerPreScan) { final Observable<List<BluetoothDevice>> devices = newConfiguredObservable( new HighPowerPeripheralScanner(this, false)); return devices .flatMap(new Func1<List<BluetoothDevice>, Observable<? extends List<GattPeripheral>>>() { @Override public Observable<? extends List<GattPeripheral>> call( List<BluetoothDevice> ignoredDevices) { logger.info(LOG_TAG, "High power pre-scan completed."); return newConfiguredObservable(createLeScanner(peripheralCriteria)); } }); } else { return newConfiguredObservable(createLeScanner(peripheralCriteria)); } } else { return Observable.error(new UserDisabledBuruberiException()); } } @NonNull @Override public Scheduler getScheduler() { return scheduler; } @Override public <T> Observable<T> newConfiguredObservable(@NonNull Observable.OnSubscribe<T> onSubscribe) { return Observable.create(onSubscribe).subscribeOn(getScheduler()).doOnError(errorListener); } @Override @RequiresPermission(Manifest.permission.BLUETOOTH) public Observable<Boolean> enabled() { return this.enabled; } @Override @RequiresPermission(Manifest.permission.BLUETOOTH) public boolean isEnabled() { return (adapter != null && adapter.isEnabled()); } @Override @RequiresPermission(allOf = { Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, }) public Observable<Void> turnOn() { if (adapter == null) { return Observable.error(new ChangePowerStateException()); } final ReplaySubject<Void> turnOnMirror = ReplaySubject.createWithSize(1); final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int oldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.ERROR); final int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (oldState == BluetoothAdapter.STATE_OFF && newState == BluetoothAdapter.STATE_TURNING_ON) { logger.info(LOG_TAG, "Bluetooth turning on"); } else if (oldState == BluetoothAdapter.STATE_TURNING_ON && newState == BluetoothAdapter.STATE_ON) { logger.info(LOG_TAG, "Bluetooth turned on"); applicationContext.unregisterReceiver(this); turnOnMirror.onNext(null); turnOnMirror.onCompleted(); } else { logger.info(LOG_TAG, "Bluetooth failed to turn on"); applicationContext.unregisterReceiver(this); turnOnMirror.onError(new ChangePowerStateException()); } } }; applicationContext.registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); if (!adapter.enable()) { applicationContext.unregisterReceiver(receiver); return Observable.error(new ChangePowerStateException()); } return turnOnMirror; } @Override @RequiresPermission(allOf = { Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, }) public Observable<Void> turnOff() { if (adapter == null) { return Observable.error(new ChangePowerStateException()); } final ReplaySubject<Void> turnOnMirror = ReplaySubject.createWithSize(1); final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { final int oldState = intent.getIntExtra(BluetoothAdapter.EXTRA_PREVIOUS_STATE, BluetoothAdapter.ERROR); final int newState = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); if (oldState == BluetoothAdapter.STATE_ON && newState == BluetoothAdapter.STATE_TURNING_OFF) { logger.info(LOG_TAG, "Bluetooth turning off"); } else if (oldState == BluetoothAdapter.STATE_TURNING_OFF && newState == BluetoothAdapter.STATE_OFF) { logger.info(LOG_TAG, "Bluetooth turned off"); applicationContext.unregisterReceiver(this); turnOnMirror.onNext(null); turnOnMirror.onCompleted(); } else { logger.info(LOG_TAG, "Bluetooth failed to turn off"); applicationContext.unregisterReceiver(this); turnOnMirror.onError(new ChangePowerStateException()); } } }; applicationContext.registerReceiver(receiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)); if (!adapter.disable()) { applicationContext.unregisterReceiver(receiver); return Observable.error(new ChangePowerStateException()); } return turnOnMirror; } @Override @NonNull public LoggerFacade getLogger() { return logger; } @Nullable @Override public Parcelable saveState(@NonNull GattPeripheral peripheral) { logger.debug(LOG_TAG, "saveState(" + peripheral + ")"); if (!(peripheral instanceof NativeGattPeripheral)) { throw new IllegalArgumentException("Peripheral is not from this bluetooth stack"); } final NativeGattPeripheral nativePeripheral = (NativeGattPeripheral) peripheral; final Bundle savedState = new Bundle(3); savedState.putParcelable(SAVED_DEVICE, nativePeripheral.bluetoothDevice); savedState.putParcelable(SAVED_ADVERTISING_DATA, peripheral.getAdvertisingData()); savedState.putInt(SAVED_RSSI, peripheral.getScanTimeRssi()); return savedState; } @Override public GattPeripheral restoreState(@Nullable Parcelable state) { logger.debug(LOG_TAG, "restoreState(" + state + ")"); if (state == null) { return null; } else { if (!(state instanceof Bundle)) { throw new IllegalArgumentException("Saved state is not from this bluetooth stack"); } final Bundle savedState = (Bundle) state; final BluetoothDevice bluetoothDevice = savedState.getParcelable(SAVED_DEVICE); final AdvertisingData advertisingData = savedState.getParcelable(SAVED_ADVERTISING_DATA); final int rssi = savedState.getInt(SAVED_RSSI, 0); if (bluetoothDevice == null || advertisingData == null) { throw new IllegalArgumentException("Saved state malformed"); } return new NativeGattPeripheral(this, bluetoothDevice, rssi, advertisingData); } } @Override public String toString() { return "AndroidBluetoothStack{" + "applicationContext=" + applicationContext + ", scheduler=" + scheduler + ", adapter=" + adapter + '}'; } }