Index: device/bluetooth/android/java/src/org/chromium/device/bluetooth/BluetoothAdapter.java |
diff --git a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/BluetoothAdapter.java b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/BluetoothAdapter.java |
index 3c8b26521f600895c60f8deadbf46b72bfc27f73..b6dca7909f143c8f03c90672936ad38909dfe20a 100644 |
--- a/device/bluetooth/android/java/src/org/chromium/device/bluetooth/BluetoothAdapter.java |
+++ b/device/bluetooth/android/java/src/org/chromium/device/bluetooth/BluetoothAdapter.java |
@@ -5,66 +5,107 @@ |
package org.chromium.device.bluetooth; |
import android.Manifest; |
+import android.annotation.TargetApi; |
+import android.bluetooth.le.ScanCallback; |
+import android.bluetooth.le.ScanResult; |
+import android.bluetooth.le.ScanSettings; |
import android.content.Context; |
import android.content.ContextWrapper; |
import android.content.pm.PackageManager; |
+import android.os.Build; |
import org.chromium.base.CalledByNative; |
import org.chromium.base.JNINamespace; |
import org.chromium.base.Log; |
+import java.util.List; |
+ |
/** |
* Exposes android.bluetooth.BluetoothAdapter as necessary for C++ |
* device::BluetoothAdapterAndroid. |
*/ |
@JNINamespace("device") |
+@TargetApi(Build.VERSION_CODES.LOLLIPOP) |
final class BluetoothAdapter { |
private static final String TAG = Log.makeTag("Bluetooth"); |
- private final boolean mHasBluetoothPermission; |
+ private final long mNativeCPPObject; |
Ted C
2015/05/27 01:13:39
Normally, we'd do mNativeBluetoothAdapter
scheib
2015/05/27 02:21:54
Done.
|
+ private final boolean mHasBluetoothCapability; |
private android.bluetooth.BluetoothAdapter mAdapter; |
+ private int mNumDiscoverySessions; |
+ private ScanCallback mLeScanCallback; |
+ |
+ // --------------------------------------------------------------------------------------------- |
+ // Construction: |
@CalledByNative |
- private static BluetoothAdapter create(Context context) { |
- return new BluetoothAdapter(context); |
+ private static BluetoothAdapter create(Context context, long nativeCPPObject) { |
+ return new BluetoothAdapter(context, nativeCPPObject); |
} |
@CalledByNative |
- private static BluetoothAdapter createWithoutPermissionForTesting(Context context) { |
+ private static BluetoothAdapter createWithoutPermissionForTesting( |
+ Context context, long nativeCPPObject) { |
Context contextWithoutPermission = new ContextWrapper(context) { |
@Override |
public int checkCallingOrSelfPermission(String permission) { |
return PackageManager.PERMISSION_DENIED; |
} |
}; |
- return new BluetoothAdapter(contextWithoutPermission); |
+ return new BluetoothAdapter(contextWithoutPermission, nativeCPPObject); |
} |
// Constructs a BluetoothAdapter. |
- private BluetoothAdapter(Context context) { |
- mHasBluetoothPermission = |
+ private BluetoothAdapter(Context context, long nativeCPPObject) { |
+ mNativeCPPObject = nativeCPPObject; |
+ final boolean hasMinAPI = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; |
+ final boolean hasPermissions = |
context.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH) |
== PackageManager.PERMISSION_GRANTED |
&& context.checkCallingOrSelfPermission(Manifest.permission.BLUETOOTH_ADMIN) |
== PackageManager.PERMISSION_GRANTED; |
- if (!mHasBluetoothPermission) { |
- Log.w(TAG, |
- "Bluetooth API disabled; BLUETOOTH and BLUETOOTH_ADMIN permissions required."); |
+ final boolean hasLowEnergyFeature = |
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 |
+ && context.getPackageManager().hasSystemFeature( |
+ PackageManager.FEATURE_BLUETOOTH_LE); |
+ mHasBluetoothCapability = hasMinAPI && hasPermissions && hasLowEnergyFeature; |
+ if (!mHasBluetoothCapability) { |
+ if (!hasMinAPI) { |
+ Log.i(TAG, "Bluetooth API disabled; SDK version (%d) too low.", |
+ Build.VERSION.SDK_INT); |
+ } else if (!hasPermissions) { |
+ Log.w(TAG, "Bluetooth API disabled; BLUETOOTH and BLUETOOTH_ADMIN permissions " |
+ + "required."); |
Ted C
2015/05/27 01:13:39
this is indented too much...should be 8
scheib
2015/05/27 02:21:54
This is enforced by 'git cl format', and reset if
|
+ } else if (!hasLowEnergyFeature) { |
+ Log.i(TAG, "Bluetooth API disabled; Low Energy not supported on system."); |
+ } |
return; |
} |
mAdapter = android.bluetooth.BluetoothAdapter.getDefaultAdapter(); |
- if (mAdapter == null) Log.i(TAG, "No adapter found."); |
+ if (mAdapter == null) { |
+ Log.i(TAG, "No adapter found."); |
+ } else { |
+ Log.i(TAG, "BluetoothAdapter successfully constructed."); |
+ } |
} |
- @CalledByNative |
- private boolean hasBluetoothPermission() { |
- return mHasBluetoothPermission; |
- } |
+ // --------------------------------------------------------------------------------------------- |
+ // BluetoothAdapterAndroid C++ methods declared for access from java: |
+ |
+ // Binds to BluetoothAdapterAndroid::OnScanFailed. |
+ private native void nativeOnScanFailed(long nativeBluetoothAdapterAndroid); |
Ted C
2015/05/27 01:13:39
We typically put native ones at the bottom since t
scheib
2015/05/27 02:21:54
Done.
|
// --------------------------------------------------------------------------------------------- |
- // BluetoothAdapterAndroid.h interface: |
+ // BluetoothAdapterAndroid methods implemented in java: |
+ // Implements BluetoothAdapterAndroid::HasBluetoothCapability. |
+ @CalledByNative |
+ private boolean hasBluetoothCapability() { |
+ return mHasBluetoothCapability; |
+ } |
+ |
+ // Implements BluetoothAdapterAndroid::GetAddress. |
@CalledByNative |
private String getAddress() { |
if (isPresent()) { |
@@ -74,6 +115,7 @@ final class BluetoothAdapter { |
} |
} |
+ // Implements BluetoothAdapterAndroid::GetName. |
@CalledByNative |
private String getName() { |
if (isPresent()) { |
@@ -83,16 +125,19 @@ final class BluetoothAdapter { |
} |
} |
+ // Implements BluetoothAdapterAndroid::IsPresent. |
@CalledByNative |
private boolean isPresent() { |
return mAdapter != null; |
} |
+ // Implements BluetoothAdapterAndroid::IsPowered. |
@CalledByNative |
private boolean isPowered() { |
return isPresent() && mAdapter.isEnabled(); |
} |
+ // Implements BluetoothAdapterAndroid::IsDiscoverable. |
@CalledByNative |
private boolean isDiscoverable() { |
return isPresent() |
@@ -100,8 +145,88 @@ final class BluetoothAdapter { |
== android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; |
} |
+ // Implements BluetoothAdapterAndroid::IsDiscovering. |
@CalledByNative |
private boolean isDiscovering() { |
return isPresent() && mAdapter.isDiscovering(); |
} |
+ |
+ // Implements BluetoothAdapterAndroid::AddDiscoverySession. |
+ @CalledByNative |
+ private boolean addDiscoverySession() { |
+ if (!isPowered()) { |
+ Log.d(TAG, "addDiscoverySession: Fails: !isPowered"); |
+ return false; |
+ } |
+ |
+ mNumDiscoverySessions++; |
+ Log.d(TAG, "addDiscoverySession: Now %d sessions", mNumDiscoverySessions); |
+ if (mNumDiscoverySessions > 1) { |
+ return true; |
+ } |
+ |
+ // ScanSettings Note: SCAN_FAILED_FEATURE_UNSUPPORTED is caused (at least on some devices) |
+ // if setReportDelay() is used or if SCAN_MODE_LOW_LATENCY isn't used. |
+ ScanSettings scanSettings = |
+ new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build(); |
+ |
+ if (mLeScanCallback == null) { |
+ mLeScanCallback = new DiscoveryScanCallback(); |
+ } |
+ mAdapter.getBluetoothLeScanner().startScan( |
+ null /* filters */, scanSettings, mLeScanCallback); |
+ return true; |
+ } |
+ |
+ // Implements BluetoothAdapterAndroid::RemoveDiscoverySession. |
+ @CalledByNative |
+ private boolean removeDiscoverySession() { |
Ted C
2015/05/27 01:13:39
is this guaranteed to be called if during the dest
scheib
2015/05/27 02:21:54
Done. The scan will now be stopped upon C++ object
|
+ if (mNumDiscoverySessions == 0) { |
+ assert false; |
+ Log.w(TAG, "removeDiscoverySession: No scan in progress."); |
+ return false; |
+ } |
+ |
+ --mNumDiscoverySessions; |
+ |
+ if (mNumDiscoverySessions == 0) { |
+ Log.d(TAG, "removeDiscoverySession: Stopping scan."); |
+ mAdapter.getBluetoothLeScanner().stopScan(mLeScanCallback); |
+ } else { |
+ Log.d(TAG, "removeDiscoverySession: Now %d sessions", mNumDiscoverySessions); |
+ } |
+ |
+ return true; |
+ } |
+ |
+ // --------------------------------------------------------------------------------------------- |
+ // Implementation details: |
+ |
+ private class DiscoveryScanCallback extends ScanCallback { |
+ @Override |
+ public void onBatchScanResults(List<ScanResult> results) { |
+ Log.v(TAG, "onBatchScanResults"); |
+ } |
+ |
+ @Override |
+ public void onScanResult(int callbackType, ScanResult result) { |
+ Log.v(TAG, "onScanResult %s %s", result.getDevice().getAddress(), |
+ result.getDevice().getName()); |
+ } |
+ |
+ @Override |
+ public void onScanFailed(int errorCode) { |
+ Log.w(TAG, "onScanFailed: %d", errorCode); |
+ // DISCUSS IN CODE REVIEW. |
+ // |
+ // TODO(scheib): Current device/bluetooth API doesn't support a way to communicate |
+ // this asynchronous failure. If there was a way to communicate asynchronous |
+ // success, then the response to AddDiscoverySession would be delayed until then or |
+ // this error. But without only the error we must presume success. |
+ // |
+ // |
+ // NEED ISSUE NUMBER. |
+ nativeOnScanFailed(mNativeCPPObject); |
+ } |
+ } |
} |