bluetooth: Android adapter can be created with and without Bluetooth permission.

This enables unit tests to run with Bluetooth permission,
by adding the Bluetooth permission to native_test.

Non-test applications will not have the Bluetooth permission,
and unit tests should also verify behavior
when the permission is not given. To enable this
createWithoutPermissionForTesting is added and
results in a state equivalent to when the permission
is not available.

Unit tests will be built in parallel for both when Bluetooth
permission exists and doesn't.

BUG=471536

Review URL: https://codereview.chromium.org/1129683002

Cr-Commit-Position: refs/heads/master@{#329212}
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 07f6349..3c8b265 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
@@ -4,7 +4,9 @@
 
 package org.chromium.device.bluetooth;
 
+import android.Manifest;
 import android.content.Context;
+import android.content.ContextWrapper;
 import android.content.pm.PackageManager;
 
 import org.chromium.base.CalledByNative;
@@ -20,6 +22,7 @@
     private static final String TAG = Log.makeTag("Bluetooth");
 
     private final boolean mHasBluetoothPermission;
+    private android.bluetooth.BluetoothAdapter mAdapter;
 
     @CalledByNative
     private static BluetoothAdapter create(Context context) {
@@ -27,16 +30,78 @@
     }
 
     @CalledByNative
+    private static BluetoothAdapter createWithoutPermissionForTesting(Context context) {
+        Context contextWithoutPermission = new ContextWrapper(context) {
+            @Override
+            public int checkCallingOrSelfPermission(String permission) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+        };
+        return new BluetoothAdapter(contextWithoutPermission);
+    }
+
+    // Constructs a BluetoothAdapter.
+    private BluetoothAdapter(Context context) {
+        mHasBluetoothPermission =
+                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.");
+            return;
+        }
+
+        mAdapter = android.bluetooth.BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) Log.i(TAG, "No adapter found.");
+    }
+
+    @CalledByNative
     private boolean hasBluetoothPermission() {
         return mHasBluetoothPermission;
     }
 
-    private BluetoothAdapter(Context context) {
-        mHasBluetoothPermission =
-                context.checkCallingOrSelfPermission(android.Manifest.permission.BLUETOOTH)
-                == PackageManager.PERMISSION_GRANTED;
-        if (!mHasBluetoothPermission) {
-            Log.w(TAG, "Can not use bluetooth API, requires BLUETOOTH permission.");
+    // ---------------------------------------------------------------------------------------------
+    // BluetoothAdapterAndroid.h interface:
+
+    @CalledByNative
+    private String getAddress() {
+        if (isPresent()) {
+            return mAdapter.getAddress();
+        } else {
+            return "";
         }
     }
+
+    @CalledByNative
+    private String getName() {
+        if (isPresent()) {
+            return mAdapter.getName();
+        } else {
+            return "";
+        }
+    }
+
+    @CalledByNative
+    private boolean isPresent() {
+        return mAdapter != null;
+    }
+
+    @CalledByNative
+    private boolean isPowered() {
+        return isPresent() && mAdapter.isEnabled();
+    }
+
+    @CalledByNative
+    private boolean isDiscoverable() {
+        return isPresent()
+                && mAdapter.getScanMode()
+                == android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE;
+    }
+
+    @CalledByNative
+    private boolean isDiscovering() {
+        return isPresent() && mAdapter.isDiscovering();
+    }
 }
diff --git a/device/bluetooth/bluetooth_adapter_android.cc b/device/bluetooth/bluetooth_adapter_android.cc
index 0d4f1408..0b8bea3 100644
--- a/device/bluetooth/bluetooth_adapter_android.cc
+++ b/device/bluetooth/bluetooth_adapter_android.cc
@@ -5,6 +5,7 @@
 #include "device/bluetooth/bluetooth_adapter_android.h"
 
 #include "base/android/jni_android.h"
+#include "base/android/jni_string.h"
 #include "base/sequenced_task_runner.h"
 #include "base/single_thread_task_runner.h"
 #include "base/thread_task_runner_handle.h"
@@ -12,6 +13,7 @@
 #include "jni/BluetoothAdapter_jni.h"
 
 using base::android::AttachCurrentThread;
+using base::android::ConvertJavaStringToUTF8;
 
 namespace device {
 
@@ -25,6 +27,17 @@
 base::WeakPtr<BluetoothAdapterAndroid>
 BluetoothAdapterAndroid::CreateAdapter() {
   BluetoothAdapterAndroid* adapter = new BluetoothAdapterAndroid();
+  adapter->j_bluetooth_adapter_.Reset(Java_BluetoothAdapter_create(
+      AttachCurrentThread(), base::android::GetApplicationContext()));
+  return adapter->weak_ptr_factory_.GetWeakPtr();
+}
+
+base::WeakPtr<BluetoothAdapterAndroid>
+BluetoothAdapterAndroid::CreateAdapterWithoutPermissionForTesting() {
+  BluetoothAdapterAndroid* adapter = new BluetoothAdapterAndroid();
+  adapter->j_bluetooth_adapter_.Reset(
+      Java_BluetoothAdapter_createWithoutPermissionForTesting(
+          AttachCurrentThread(), base::android::GetApplicationContext()));
   return adapter->weak_ptr_factory_.GetWeakPtr();
 }
 
@@ -39,11 +52,13 @@
 }
 
 std::string BluetoothAdapterAndroid::GetAddress() const {
-  return address_;
+  return ConvertJavaStringToUTF8(Java_BluetoothAdapter_getAddress(
+      AttachCurrentThread(), j_bluetooth_adapter_.obj()));
 }
 
 std::string BluetoothAdapterAndroid::GetName() const {
-  return name_;
+  return ConvertJavaStringToUTF8(Java_BluetoothAdapter_getName(
+      AttachCurrentThread(), j_bluetooth_adapter_.obj()));
 }
 
 void BluetoothAdapterAndroid::SetName(const std::string& name,
@@ -58,13 +73,13 @@
 }
 
 bool BluetoothAdapterAndroid::IsPresent() const {
-  NOTIMPLEMENTED();
-  return false;
+  return Java_BluetoothAdapter_isPresent(AttachCurrentThread(),
+                                         j_bluetooth_adapter_.obj());
 }
 
 bool BluetoothAdapterAndroid::IsPowered() const {
-  NOTIMPLEMENTED();
-  return false;
+  return Java_BluetoothAdapter_isPowered(AttachCurrentThread(),
+                                         j_bluetooth_adapter_.obj());
 }
 
 void BluetoothAdapterAndroid::SetPowered(bool powered,
@@ -74,8 +89,8 @@
 }
 
 bool BluetoothAdapterAndroid::IsDiscoverable() const {
-  NOTIMPLEMENTED();
-  return false;
+  return Java_BluetoothAdapter_isDiscoverable(AttachCurrentThread(),
+                                              j_bluetooth_adapter_.obj());
 }
 
 void BluetoothAdapterAndroid::SetDiscoverable(
@@ -86,8 +101,8 @@
 }
 
 bool BluetoothAdapterAndroid::IsDiscovering() const {
-  NOTIMPLEMENTED();
-  return false;
+  return Java_BluetoothAdapter_isDiscovering(AttachCurrentThread(),
+                                             j_bluetooth_adapter_.obj());
 }
 
 void BluetoothAdapterAndroid::CreateRfcommService(
@@ -123,8 +138,6 @@
 }
 
 BluetoothAdapterAndroid::BluetoothAdapterAndroid() : weak_ptr_factory_(this) {
-  j_bluetooth_adapter_.Reset(Java_BluetoothAdapter_create(
-      AttachCurrentThread(), base::android::GetApplicationContext()));
 }
 
 BluetoothAdapterAndroid::~BluetoothAdapterAndroid() {
diff --git a/device/bluetooth/bluetooth_adapter_android.h b/device/bluetooth/bluetooth_adapter_android.h
index cfb9d31..d458bb56 100644
--- a/device/bluetooth/bluetooth_adapter_android.h
+++ b/device/bluetooth/bluetooth_adapter_android.h
@@ -23,6 +23,10 @@
   // Create a BluetoothAdapterAndroid instance.
   static base::WeakPtr<BluetoothAdapterAndroid> CreateAdapter();
 
+  // Create a BluetoothAdapterAndroid instance without Bluetooth permission.
+  static base::WeakPtr<BluetoothAdapterAndroid>
+  CreateAdapterWithoutPermissionForTesting();
+
   // Register C++ methods exposed to Java using JNI.
   static bool RegisterJNI(JNIEnv* env);
 
diff --git a/device/bluetooth/bluetooth_adapter_android_unittest.cc b/device/bluetooth/bluetooth_adapter_android_unittest.cc
index c4d5252..1df32a2 100644
--- a/device/bluetooth/bluetooth_adapter_android_unittest.cc
+++ b/device/bluetooth/bluetooth_adapter_android_unittest.cc
@@ -10,16 +10,45 @@
 
 class BluetoothAdapterAndroidTest : public testing::Test {
  protected:
-  BluetoothAdapterAndroidTest() {
+  void InitWithPermission() {
     adapter_ = BluetoothAdapterAndroid::CreateAdapter().get();
   }
 
+  void InitWithoutPermission() {
+    adapter_ =
+        BluetoothAdapterAndroid::CreateAdapterWithoutPermissionForTesting()
+            .get();
+  }
+
   scoped_refptr<BluetoothAdapterAndroid> adapter_;
 };
 
 TEST_F(BluetoothAdapterAndroidTest, Construct) {
+  InitWithPermission();
+  ASSERT_TRUE(adapter_.get());
+  EXPECT_TRUE(adapter_->HasBluetoothPermission());
+  if (!adapter_->IsPresent()) {
+    LOG(WARNING) << "Bluetooth adapter not present; skipping unit test.";
+    return;
+  }
+  EXPECT_GT(adapter_->GetAddress().length(), 0u);
+  EXPECT_GT(adapter_->GetName().length(), 0u);
+  EXPECT_TRUE(adapter_->IsPresent());
+  EXPECT_TRUE(adapter_->IsPowered());
+  EXPECT_FALSE(adapter_->IsDiscoverable());
+  EXPECT_FALSE(adapter_->IsDiscovering());
+}
+
+TEST_F(BluetoothAdapterAndroidTest, ConstructNoPermision) {
+  InitWithoutPermission();
   ASSERT_TRUE(adapter_.get());
   EXPECT_FALSE(adapter_->HasBluetoothPermission());
+  EXPECT_EQ(adapter_->GetAddress().length(), 0u);
+  EXPECT_EQ(adapter_->GetName().length(), 0u);
+  EXPECT_FALSE(adapter_->IsPresent());
+  EXPECT_FALSE(adapter_->IsPowered());
+  EXPECT_FALSE(adapter_->IsDiscoverable());
+  EXPECT_FALSE(adapter_->IsDiscovering());
 }
 
 }  // namespace device
diff --git a/testing/android/native_test/java/AndroidManifest.xml b/testing/android/native_test/java/AndroidManifest.xml
index a92fb5f..37cc82dc 100644
--- a/testing/android/native_test/java/AndroidManifest.xml
+++ b/testing/android/native_test/java/AndroidManifest.xml
@@ -11,7 +11,8 @@
       android:versionName="1.0">
 
     <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="22" />
-
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.INTERNET"/>
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>