summaryrefslogtreecommitdiffstats
path: root/device/bluetooth/android/java/src/org/chromium/device/bluetooth/ChromeBluetoothDevice.java
blob: 88e290f41d7fb6d751fbce861735f768dfecd92a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.device.bluetooth;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.Build;
import android.os.ParcelUuid;

import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

/**
 * Exposes android.bluetooth.BluetoothDevice as necessary for C++
 * device::BluetoothDeviceAndroid.
 *
 * Lifetime is controlled by device::BluetoothDeviceAndroid.
 */
@JNINamespace("device")
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
final class ChromeBluetoothDevice {
    private static final String TAG = "Bluetooth";

    private long mNativeBluetoothDeviceAndroid;
    final Wrappers.BluetoothDeviceWrapper mDevice;
    private HashSet<String> mUuidsFromScan;
    Wrappers.BluetoothGattWrapper mBluetoothGatt;
    private final BluetoothGattCallbackImpl mBluetoothGattCallbackImpl;
    final HashMap<Wrappers.BluetoothGattCharacteristicWrapper,
            ChromeBluetoothRemoteGattCharacteristic> mWrapperToChromeCharacteristicsMap;
    final HashMap<Wrappers.BluetoothGattDescriptorWrapper, ChromeBluetoothRemoteGattDescriptor>
            mWrapperToChromeDescriptorsMap;

    private ChromeBluetoothDevice(
            long nativeBluetoothDeviceAndroid, Wrappers.BluetoothDeviceWrapper deviceWrapper) {
        mNativeBluetoothDeviceAndroid = nativeBluetoothDeviceAndroid;
        mDevice = deviceWrapper;
        mUuidsFromScan = new HashSet<String>();
        mBluetoothGattCallbackImpl = new BluetoothGattCallbackImpl();
        mWrapperToChromeCharacteristicsMap =
                new HashMap<Wrappers.BluetoothGattCharacteristicWrapper,
                        ChromeBluetoothRemoteGattCharacteristic>();
        mWrapperToChromeDescriptorsMap = new HashMap<Wrappers.BluetoothGattDescriptorWrapper,
                ChromeBluetoothRemoteGattDescriptor>();
        Log.v(TAG, "ChromeBluetoothDevice created.");
    }

    /**
     * Handles C++ object being destroyed.
     */
    @CalledByNative
    private void onBluetoothDeviceAndroidDestruction() {
        if (mBluetoothGatt != null) {
            mBluetoothGatt.close();
            mBluetoothGatt = null;
        }
        mNativeBluetoothDeviceAndroid = 0;
    }

    // ---------------------------------------------------------------------------------------------
    // BluetoothDeviceAndroid methods implemented in java:

    // Implements BluetoothDeviceAndroid::Create.
    // 'Object' type must be used because inner class Wrappers.BluetoothDeviceWrapper reference is
    // not handled by jni_generator.py JavaToJni. http://crbug.com/505554
    @CalledByNative
    private static ChromeBluetoothDevice create(
            long nativeBluetoothDeviceAndroid, Object deviceWrapper) {
        return new ChromeBluetoothDevice(
                nativeBluetoothDeviceAndroid, (Wrappers.BluetoothDeviceWrapper) deviceWrapper);
    }

    // Implements BluetoothDeviceAndroid::UpdateAdvertisedUUIDs.
    @CalledByNative
    private boolean updateAdvertisedUUIDs(List<ParcelUuid> uuidsFromScan) {
        if (uuidsFromScan == null) {
            return false;
        }
        boolean uuidsUpdated = false;
        for (ParcelUuid uuid : uuidsFromScan) {
            uuidsUpdated |= mUuidsFromScan.add(uuid.toString());
        }
        return uuidsUpdated;
    }

    // Implements BluetoothDeviceAndroid::GetBluetoothClass.
    @CalledByNative
    private int getBluetoothClass() {
        return mDevice.getBluetoothClass_getDeviceClass();
    }

    // Implements BluetoothDeviceAndroid::GetAddress.
    @CalledByNative
    private String getAddress() {
        return mDevice.getAddress();
    }

    // Implements BluetoothDeviceAndroid::IsPaired.
    @CalledByNative
    private boolean isPaired() {
        return mDevice.getBondState() == BluetoothDevice.BOND_BONDED;
    }

    // Implements BluetoothDeviceAndroid::GetUUIDs.
    @CalledByNative
    private String[] getUuids() {
        // TODO(scheib): return merged list of UUIDs from scan results and,
        // after a device is connected, discoverServices. crbug.com/508648
        return mUuidsFromScan.toArray(new String[mUuidsFromScan.size()]);
    }

    // Implements BluetoothDeviceAndroid::CreateGattConnectionImpl.
    @CalledByNative
    private void createGattConnectionImpl(Context context) {
        Log.i(TAG, "connectGatt");

        if (mBluetoothGatt != null) mBluetoothGatt.close();

        // autoConnect set to false as under experimentation using autoConnect failed to complete
        // connections.
        mBluetoothGatt =
                mDevice.connectGatt(context, false /* autoConnect */, mBluetoothGattCallbackImpl);
    }

    // Implements BluetoothDeviceAndroid::DisconnectGatt.
    @CalledByNative
    private void disconnectGatt() {
        Log.i(TAG, "BluetoothGatt.disconnect");
        if (mBluetoothGatt != null) mBluetoothGatt.disconnect();
    }

    // Implements BluetoothDeviceAndroid::GetDeviceName.
    @CalledByNative
    private String getDeviceName() {
        return mDevice.getName();
    }

    // Implements callbacks related to a GATT connection.
    private class BluetoothGattCallbackImpl extends Wrappers.BluetoothGattCallbackWrapper {
        @Override
        public void onConnectionStateChange(final int status, final int newState) {
            Log.i(TAG, "onConnectionStateChange status:%d newState:%s", status,
                    (newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED)
                            ? "Connected"
                            : "Disconnected");
            if (newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED) {
                mBluetoothGatt.discoverServices();
            } else if (newState == android.bluetooth.BluetoothProfile.STATE_DISCONNECTED) {
                if (mBluetoothGatt != null) {
                    mBluetoothGatt.close();
                    mBluetoothGatt = null;
                }
            }
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (mNativeBluetoothDeviceAndroid != 0) {
                        nativeOnConnectionStateChange(mNativeBluetoothDeviceAndroid, status,
                                newState == android.bluetooth.BluetoothProfile.STATE_CONNECTED);
                    }
                }
            });
        }

        @Override
        public void onServicesDiscovered(final int status) {
            Log.i(TAG, "onServicesDiscovered status:%d==%s", status,
                    status == android.bluetooth.BluetoothGatt.GATT_SUCCESS ? "OK" : "Error");
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (mNativeBluetoothDeviceAndroid != 0) {
                        // TODO(crbug.com/576906): Update or replace existing GATT objects if they
                        //                         change after initial discovery.
                        for (Wrappers.BluetoothGattServiceWrapper service :
                                mBluetoothGatt.getServices()) {
                            // Create an adapter unique service ID. getInstanceId only differs
                            // between service instances with the same UUID on this device.
                            String serviceInstanceId = getAddress() + "/"
                                    + service.getUuid().toString() + "," + service.getInstanceId();
                            nativeCreateGattRemoteService(
                                    mNativeBluetoothDeviceAndroid, serviceInstanceId, service);
                        }
                        nativeOnGattServicesDiscovered(mNativeBluetoothDeviceAndroid);
                    }
                }
            });
        }

        @Override
        public void onCharacteristicChanged(
                final Wrappers.BluetoothGattCharacteristicWrapper characteristic) {
            Log.i(TAG, "device onCharacteristicChanged.");
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ChromeBluetoothRemoteGattCharacteristic chromeCharacteristic =
                            mWrapperToChromeCharacteristicsMap.get(characteristic);
                    if (chromeCharacteristic == null) {
                        // Android events arriving with no Chrome object is expected rarely only
                        // when the event races object destruction.
                        Log.v(TAG, "onCharacteristicChanged when chromeCharacteristic == null.");
                    } else {
                        chromeCharacteristic.onCharacteristicChanged();
                    }
                }
            });
        }

        @Override
        public void onCharacteristicRead(
                final Wrappers.BluetoothGattCharacteristicWrapper characteristic,
                final int status) {
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ChromeBluetoothRemoteGattCharacteristic chromeCharacteristic =
                            mWrapperToChromeCharacteristicsMap.get(characteristic);
                    if (chromeCharacteristic == null) {
                        // Android events arriving with no Chrome object is expected rarely: only
                        // when the event races object destruction.
                        Log.v(TAG, "onCharacteristicRead when chromeCharacteristic == null.");
                    } else {
                        chromeCharacteristic.onCharacteristicRead(status);
                    }
                }
            });
        }

        @Override
        public void onCharacteristicWrite(
                final Wrappers.BluetoothGattCharacteristicWrapper characteristic,
                final int status) {
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    ChromeBluetoothRemoteGattCharacteristic chromeCharacteristic =
                            mWrapperToChromeCharacteristicsMap.get(characteristic);
                    if (chromeCharacteristic == null) {
                        // Android events arriving with no Chrome object is expected rarely: only
                        // when the event races object destruction.
                        Log.v(TAG, "onCharacteristicWrite when chromeCharacteristic == null.");
                    } else {
                        chromeCharacteristic.onCharacteristicWrite(status);
                    }
                }
            });
        }
    }

    // ---------------------------------------------------------------------------------------------
    // BluetoothAdapterDevice C++ methods declared for access from java:

    // Binds to BluetoothDeviceAndroid::OnConnectionStateChange.
    private native void nativeOnConnectionStateChange(
            long nativeBluetoothDeviceAndroid, int status, boolean connected);

    // Binds to BluetoothDeviceAndroid::CreateGattRemoteService.
    // TODO(http://crbug.com/505554): Replace 'Object' with specific type when JNI fixed.
    private native void nativeCreateGattRemoteService(long nativeBluetoothDeviceAndroid,
            String instanceId, Object bluetoothGattServiceWrapper);

    // Binds to BluetoothDeviceAndroid::GattServicesDiscovered.
    private native void nativeOnGattServicesDiscovered(long nativeBluetoothDeviceAndroid);
}