 Chromium Code Reviews
 Chromium Code Reviews Issue 1150833002:
  bluetooth: android: Initial Low Energy Discovery Sessions.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@bta-manifest-
    
  
    Issue 1150833002:
  bluetooth: android: Initial Low Energy Discovery Sessions.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@bta-manifest-| OLD | NEW | 
|---|---|
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be | 
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. | 
| 4 | 4 | 
| 5 package org.chromium.device.bluetooth; | 5 package org.chromium.device.bluetooth; | 
| 6 | 6 | 
| 7 import android.Manifest; | 7 import android.Manifest; | 
| 8 import android.annotation.TargetApi; | |
| 9 import android.bluetooth.le.ScanCallback; | |
| 10 import android.bluetooth.le.ScanResult; | |
| 11 import android.bluetooth.le.ScanSettings; | |
| 8 import android.content.Context; | 12 import android.content.Context; | 
| 9 import android.content.ContextWrapper; | 13 import android.content.ContextWrapper; | 
| 10 import android.content.pm.PackageManager; | 14 import android.content.pm.PackageManager; | 
| 15 import android.os.Build; | |
| 11 | 16 | 
| 12 import org.chromium.base.CalledByNative; | 17 import org.chromium.base.CalledByNative; | 
| 13 import org.chromium.base.JNINamespace; | 18 import org.chromium.base.JNINamespace; | 
| 14 import org.chromium.base.Log; | 19 import org.chromium.base.Log; | 
| 15 | 20 | 
| 21 import java.util.List; | |
| 22 | |
| 16 /** | 23 /** | 
| 17 * Exposes android.bluetooth.BluetoothAdapter as necessary for C++ | 24 * Exposes android.bluetooth.BluetoothAdapter as necessary for C++ | 
| 18 * device::BluetoothAdapterAndroid. | 25 * device::BluetoothAdapterAndroid. | 
| 19 */ | 26 */ | 
| 20 @JNINamespace("device") | 27 @JNINamespace("device") | 
| 28 @TargetApi(Build.VERSION_CODES.LOLLIPOP) | |
| 21 final class BluetoothAdapter { | 29 final class BluetoothAdapter { | 
| 22 private static final String TAG = Log.makeTag("Bluetooth"); | 30 private static final String TAG = Log.makeTag("Bluetooth"); | 
| 23 | 31 | 
| 24 private final boolean mHasBluetoothPermission; | 32 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.
 | |
| 33 private final boolean mHasBluetoothCapability; | |
| 25 private android.bluetooth.BluetoothAdapter mAdapter; | 34 private android.bluetooth.BluetoothAdapter mAdapter; | 
| 35 private int mNumDiscoverySessions; | |
| 36 private ScanCallback mLeScanCallback; | |
| 37 | |
| 38 // ------------------------------------------------------------------------- -------------------- | |
| 39 // Construction: | |
| 26 | 40 | 
| 27 @CalledByNative | 41 @CalledByNative | 
| 28 private static BluetoothAdapter create(Context context) { | 42 private static BluetoothAdapter create(Context context, long nativeCPPObject ) { | 
| 29 return new BluetoothAdapter(context); | 43 return new BluetoothAdapter(context, nativeCPPObject); | 
| 30 } | 44 } | 
| 31 | 45 | 
| 32 @CalledByNative | 46 @CalledByNative | 
| 33 private static BluetoothAdapter createWithoutPermissionForTesting(Context co ntext) { | 47 private static BluetoothAdapter createWithoutPermissionForTesting( | 
| 48 Context context, long nativeCPPObject) { | |
| 34 Context contextWithoutPermission = new ContextWrapper(context) { | 49 Context contextWithoutPermission = new ContextWrapper(context) { | 
| 35 @Override | 50 @Override | 
| 36 public int checkCallingOrSelfPermission(String permission) { | 51 public int checkCallingOrSelfPermission(String permission) { | 
| 37 return PackageManager.PERMISSION_DENIED; | 52 return PackageManager.PERMISSION_DENIED; | 
| 38 } | 53 } | 
| 39 }; | 54 }; | 
| 40 return new BluetoothAdapter(contextWithoutPermission); | 55 return new BluetoothAdapter(contextWithoutPermission, nativeCPPObject); | 
| 41 } | 56 } | 
| 42 | 57 | 
| 43 // Constructs a BluetoothAdapter. | 58 // Constructs a BluetoothAdapter. | 
| 44 private BluetoothAdapter(Context context) { | 59 private BluetoothAdapter(Context context, long nativeCPPObject) { | 
| 45 mHasBluetoothPermission = | 60 mNativeCPPObject = nativeCPPObject; | 
| 61 final boolean hasMinAPI = Build.VERSION.SDK_INT >= Build.VERSION_CODES.L OLLIPOP; | |
| 62 final boolean hasPermissions = | |
| 46 context.checkCallingOrSelfPermission(Manifest.permission.BLUETOO TH) | 63 context.checkCallingOrSelfPermission(Manifest.permission.BLUETOO TH) | 
| 47 == PackageManager.PERMISSION_GRANTED | 64 == PackageManager.PERMISSION_GRANTED | 
| 48 && context.checkCallingOrSelfPermission(Manifest.permission.BLUE TOOTH_ADMIN) | 65 && context.checkCallingOrSelfPermission(Manifest.permission.BLUE TOOTH_ADMIN) | 
| 49 == PackageManager.PERMISSION_GRANTED; | 66 == PackageManager.PERMISSION_GRANTED; | 
| 50 if (!mHasBluetoothPermission) { | 67 final boolean hasLowEnergyFeature = | 
| 51 Log.w(TAG, | 68 Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 | 
| 52 "Bluetooth API disabled; BLUETOOTH and BLUETOOTH_ADMIN permi ssions required."); | 69 && context.getPackageManager().hasSystemFeature( | 
| 70 PackageManager.FEATURE_BLUETOOTH_LE); | |
| 71 mHasBluetoothCapability = hasMinAPI && hasPermissions && hasLowEnergyFea ture; | |
| 72 if (!mHasBluetoothCapability) { | |
| 73 if (!hasMinAPI) { | |
| 74 Log.i(TAG, "Bluetooth API disabled; SDK version (%d) too low.", | |
| 75 Build.VERSION.SDK_INT); | |
| 76 } else if (!hasPermissions) { | |
| 77 Log.w(TAG, "Bluetooth API disabled; BLUETOOTH and BLUETOOTH_ADMI N permissions " | |
| 78 + "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
 | |
| 79 } else if (!hasLowEnergyFeature) { | |
| 80 Log.i(TAG, "Bluetooth API disabled; Low Energy not supported on system."); | |
| 81 } | |
| 53 return; | 82 return; | 
| 54 } | 83 } | 
| 55 | 84 | 
| 56 mAdapter = android.bluetooth.BluetoothAdapter.getDefaultAdapter(); | 85 mAdapter = android.bluetooth.BluetoothAdapter.getDefaultAdapter(); | 
| 57 if (mAdapter == null) Log.i(TAG, "No adapter found."); | 86 if (mAdapter == null) { | 
| 58 } | 87 Log.i(TAG, "No adapter found."); | 
| 59 | 88 } else { | 
| 60 @CalledByNative | 89 Log.i(TAG, "BluetoothAdapter successfully constructed."); | 
| 61 private boolean hasBluetoothPermission() { | 90 } | 
| 62 return mHasBluetoothPermission; | |
| 63 } | 91 } | 
| 64 | 92 | 
| 65 // ------------------------------------------------------------------------- -------------------- | 93 // ------------------------------------------------------------------------- -------------------- | 
| 66 // BluetoothAdapterAndroid.h interface: | 94 // BluetoothAdapterAndroid C++ methods declared for access from java: | 
| 67 | 95 | 
| 96 // Binds to BluetoothAdapterAndroid::OnScanFailed. | |
| 97 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.
 | |
| 98 | |
| 99 // ------------------------------------------------------------------------- -------------------- | |
| 100 // BluetoothAdapterAndroid methods implemented in java: | |
| 101 | |
| 102 // Implements BluetoothAdapterAndroid::HasBluetoothCapability. | |
| 103 @CalledByNative | |
| 104 private boolean hasBluetoothCapability() { | |
| 105 return mHasBluetoothCapability; | |
| 106 } | |
| 107 | |
| 108 // Implements BluetoothAdapterAndroid::GetAddress. | |
| 68 @CalledByNative | 109 @CalledByNative | 
| 69 private String getAddress() { | 110 private String getAddress() { | 
| 70 if (isPresent()) { | 111 if (isPresent()) { | 
| 71 return mAdapter.getAddress(); | 112 return mAdapter.getAddress(); | 
| 72 } else { | 113 } else { | 
| 73 return ""; | 114 return ""; | 
| 74 } | 115 } | 
| 75 } | 116 } | 
| 76 | 117 | 
| 118 // Implements BluetoothAdapterAndroid::GetName. | |
| 77 @CalledByNative | 119 @CalledByNative | 
| 78 private String getName() { | 120 private String getName() { | 
| 79 if (isPresent()) { | 121 if (isPresent()) { | 
| 80 return mAdapter.getName(); | 122 return mAdapter.getName(); | 
| 81 } else { | 123 } else { | 
| 82 return ""; | 124 return ""; | 
| 83 } | 125 } | 
| 84 } | 126 } | 
| 85 | 127 | 
| 128 // Implements BluetoothAdapterAndroid::IsPresent. | |
| 86 @CalledByNative | 129 @CalledByNative | 
| 87 private boolean isPresent() { | 130 private boolean isPresent() { | 
| 88 return mAdapter != null; | 131 return mAdapter != null; | 
| 89 } | 132 } | 
| 90 | 133 | 
| 134 // Implements BluetoothAdapterAndroid::IsPowered. | |
| 91 @CalledByNative | 135 @CalledByNative | 
| 92 private boolean isPowered() { | 136 private boolean isPowered() { | 
| 93 return isPresent() && mAdapter.isEnabled(); | 137 return isPresent() && mAdapter.isEnabled(); | 
| 94 } | 138 } | 
| 95 | 139 | 
| 140 // Implements BluetoothAdapterAndroid::IsDiscoverable. | |
| 96 @CalledByNative | 141 @CalledByNative | 
| 97 private boolean isDiscoverable() { | 142 private boolean isDiscoverable() { | 
| 98 return isPresent() | 143 return isPresent() | 
| 99 && mAdapter.getScanMode() | 144 && mAdapter.getScanMode() | 
| 100 == android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISC OVERABLE; | 145 == android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISC OVERABLE; | 
| 101 } | 146 } | 
| 102 | 147 | 
| 148 // Implements BluetoothAdapterAndroid::IsDiscovering. | |
| 103 @CalledByNative | 149 @CalledByNative | 
| 104 private boolean isDiscovering() { | 150 private boolean isDiscovering() { | 
| 105 return isPresent() && mAdapter.isDiscovering(); | 151 return isPresent() && mAdapter.isDiscovering(); | 
| 106 } | 152 } | 
| 153 | |
| 154 // Implements BluetoothAdapterAndroid::AddDiscoverySession. | |
| 155 @CalledByNative | |
| 156 private boolean addDiscoverySession() { | |
| 157 if (!isPowered()) { | |
| 158 Log.d(TAG, "addDiscoverySession: Fails: !isPowered"); | |
| 159 return false; | |
| 160 } | |
| 161 | |
| 162 mNumDiscoverySessions++; | |
| 163 Log.d(TAG, "addDiscoverySession: Now %d sessions", mNumDiscoverySessions ); | |
| 164 if (mNumDiscoverySessions > 1) { | |
| 165 return true; | |
| 166 } | |
| 167 | |
| 168 // ScanSettings Note: SCAN_FAILED_FEATURE_UNSUPPORTED is caused (at leas t on some devices) | |
| 169 // if setReportDelay() is used or if SCAN_MODE_LOW_LATENCY isn't used. | |
| 170 ScanSettings scanSettings = | |
| 171 new ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LO W_LATENCY).build(); | |
| 172 | |
| 173 if (mLeScanCallback == null) { | |
| 174 mLeScanCallback = new DiscoveryScanCallback(); | |
| 175 } | |
| 176 mAdapter.getBluetoothLeScanner().startScan( | |
| 177 null /* filters */, scanSettings, mLeScanCallback); | |
| 178 return true; | |
| 179 } | |
| 180 | |
| 181 // Implements BluetoothAdapterAndroid::RemoveDiscoverySession. | |
| 182 @CalledByNative | |
| 183 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
 | |
| 184 if (mNumDiscoverySessions == 0) { | |
| 185 assert false; | |
| 186 Log.w(TAG, "removeDiscoverySession: No scan in progress."); | |
| 187 return false; | |
| 188 } | |
| 189 | |
| 190 --mNumDiscoverySessions; | |
| 191 | |
| 192 if (mNumDiscoverySessions == 0) { | |
| 193 Log.d(TAG, "removeDiscoverySession: Stopping scan."); | |
| 194 mAdapter.getBluetoothLeScanner().stopScan(mLeScanCallback); | |
| 195 } else { | |
| 196 Log.d(TAG, "removeDiscoverySession: Now %d sessions", mNumDiscoveryS essions); | |
| 197 } | |
| 198 | |
| 199 return true; | |
| 200 } | |
| 201 | |
| 202 // ------------------------------------------------------------------------- -------------------- | |
| 203 // Implementation details: | |
| 204 | |
| 205 private class DiscoveryScanCallback extends ScanCallback { | |
| 206 @Override | |
| 207 public void onBatchScanResults(List<ScanResult> results) { | |
| 208 Log.v(TAG, "onBatchScanResults"); | |
| 209 } | |
| 210 | |
| 211 @Override | |
| 212 public void onScanResult(int callbackType, ScanResult result) { | |
| 213 Log.v(TAG, "onScanResult %s %s", result.getDevice().getAddress(), | |
| 214 result.getDevice().getName()); | |
| 215 } | |
| 216 | |
| 217 @Override | |
| 218 public void onScanFailed(int errorCode) { | |
| 219 Log.w(TAG, "onScanFailed: %d", errorCode); | |
| 220 // DISCUSS IN CODE REVIEW. | |
| 221 // | |
| 222 // TODO(scheib): Current device/bluetooth API doesn't support a way to communicate | |
| 223 // this asynchronous failure. If there was a way to communicate asyn chronous | |
| 224 // success, then the response to AddDiscoverySession would be delaye d until then or | |
| 225 // this error. But without only the error we must presume success. | |
| 226 // | |
| 227 // | |
| 228 // NEED ISSUE NUMBER. | |
| 229 nativeOnScanFailed(mNativeCPPObject); | |
| 230 } | |
| 231 } | |
| 107 } | 232 } | 
| OLD | NEW |