OLD | NEW |
---|---|
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 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 #include "components/arc/bluetooth/arc_bluetooth_bridge.h" | 5 #include "components/arc/bluetooth/arc_bluetooth_bridge.h" |
6 | 6 |
7 #include <bluetooth/bluetooth.h> | 7 #include <bluetooth/bluetooth.h> |
8 #include <fcntl.h> | 8 #include <fcntl.h> |
9 #include <stddef.h> | 9 #include <stddef.h> |
10 #include <sys/socket.h> | 10 #include <sys/socket.h> |
(...skipping 293 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
304 // ready, we should register it now. | 304 // ready, we should register it now. |
305 if (bluetooth_adapter_ && !bluetooth_adapter_->HasObserver(this)) | 305 if (bluetooth_adapter_ && !bluetooth_adapter_->HasObserver(this)) |
306 bluetooth_adapter_->AddObserver(this); | 306 bluetooth_adapter_->AddObserver(this); |
307 } | 307 } |
308 | 308 |
309 void ArcBluetoothBridge::OnInstanceClosed() { | 309 void ArcBluetoothBridge::OnInstanceClosed() { |
310 if (bluetooth_adapter_) | 310 if (bluetooth_adapter_) |
311 bluetooth_adapter_->RemoveObserver(this); | 311 bluetooth_adapter_->RemoveObserver(this); |
312 } | 312 } |
313 | 313 |
314 void ArcBluetoothBridge::DeviceAdded(BluetoothAdapter* adapter, | 314 void ArcBluetoothBridge::SendDeviceData(const BluetoothDevice* device) const { |
Luis Héctor Chávez
2016/10/06 16:51:01
Maybe SendDevicePropertiesAndAdvData? SendDeviceIn
puthik_chromium
2016/10/06 18:29:30
SendDeviceInformation
| |
315 BluetoothDevice* device) { | |
316 auto* bluetooth_instance = | 315 auto* bluetooth_instance = |
317 arc_bridge_service()->bluetooth()->GetInstanceForMethod("OnDeviceFound"); | 316 arc_bridge_service()->bluetooth()->GetInstanceForMethod("OnDeviceFound"); |
318 if (!bluetooth_instance) | 317 if (!bluetooth_instance) |
319 return; | 318 return; |
320 | 319 |
321 mojo::Array<mojom::BluetoothPropertyPtr> properties = | 320 mojo::Array<mojom::BluetoothPropertyPtr> properties = |
322 GetDeviceProperties(mojom::BluetoothPropertyType::ALL, device); | 321 GetDeviceProperties(mojom::BluetoothPropertyType::ALL, device); |
323 | 322 |
324 bluetooth_instance->OnDeviceFound(std::move(properties)); | 323 bluetooth_instance->OnDeviceFound(std::move(properties)); |
325 | 324 |
326 auto* btle_instance = arc_bridge_service()->bluetooth()->GetInstanceForMethod( | |
327 "OnLEDeviceFound", kMinBtleVersion); | |
328 if (!btle_instance) | |
329 return; | |
330 | 325 |
331 if (!(device->GetType() & device::BLUETOOTH_TRANSPORT_LE)) | 326 if (!(device->GetType() & device::BLUETOOTH_TRANSPORT_LE)) |
332 return; | 327 return; |
333 | 328 |
334 mojom::BluetoothAddressPtr addr = | |
335 mojom::BluetoothAddress::From(device->GetAddress()); | |
336 base::Optional<int8_t> rssi = device->GetInquiryRSSI(); | 329 base::Optional<int8_t> rssi = device->GetInquiryRSSI(); |
337 mojo::Array<mojom::BluetoothAdvertisingDataPtr> adv_data = | 330 mojom::BluetoothAddressPtr addr; |
338 GetAdvertisingData(device); | 331 |
339 btle_instance->OnLEDeviceFound(std::move(addr), | 332 // We only want to send updated advertise data to Android only when we are |
340 rssi.value_or(mojom::kUnknownPower), | 333 // scanning which is checked by the validity of rssi. Here are the 2 cases |
341 std::move(adv_data)); | 334 // that we don't want to send updated advertise data to Android. |
335 // 1) Cached found device and 2) rssi became invalid when we stop scanning. | |
336 if (rssi.has_value()) { | |
337 auto* btle_instance = | |
338 arc_bridge_service()->bluetooth()->GetInstanceForMethod( | |
339 "OnLEDeviceFound", kMinBtleVersion); | |
340 if (!btle_instance) | |
341 return; | |
342 mojo::Array<mojom::BluetoothAdvertisingDataPtr> adv_data = | |
343 GetAdvertisingData(device); | |
344 addr = mojom::BluetoothAddress::From(device->GetAddress()); | |
345 btle_instance->OnLEDeviceFound(std::move(addr), rssi.value(), | |
346 std::move(adv_data)); | |
347 } | |
342 | 348 |
343 if (!device->IsConnected()) | 349 if (!device->IsConnected()) |
344 return; | 350 return; |
345 | 351 |
346 addr = mojom::BluetoothAddress::From(device->GetAddress()); | 352 addr = mojom::BluetoothAddress::From(device->GetAddress()); |
347 OnGattConnectStateChanged(std::move(addr), true); | 353 OnGattConnectStateChanged(std::move(addr), true); |
348 } | 354 } |
349 | 355 |
356 void ArcBluetoothBridge::DeviceAdded(BluetoothAdapter* adapter, | |
357 BluetoothDevice* device) { | |
358 SendDeviceData(device); | |
359 } | |
360 | |
350 void ArcBluetoothBridge::DeviceChanged(BluetoothAdapter* adapter, | 361 void ArcBluetoothBridge::DeviceChanged(BluetoothAdapter* adapter, |
351 BluetoothDevice* device) { | 362 BluetoothDevice* device) { |
363 SendDeviceData(device); | |
364 | |
352 if (!(device->GetType() & device::BLUETOOTH_TRANSPORT_LE)) | 365 if (!(device->GetType() & device::BLUETOOTH_TRANSPORT_LE)) |
353 return; | 366 return; |
354 | 367 |
355 auto it = gatt_connection_cache_.find(device->GetAddress()); | 368 auto it = gatt_connection_cache_.find(device->GetAddress()); |
356 bool was_connected = it != gatt_connection_cache_.end(); | 369 bool was_connected = it != gatt_connection_cache_.end(); |
357 bool is_connected = device->IsConnected(); | 370 bool is_connected = device->IsConnected(); |
358 | 371 |
359 if (is_connected == was_connected) | 372 if (is_connected == was_connected) |
360 return; | 373 return; |
361 | 374 |
(...skipping 1335 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1697 mojom::BluetoothBondState bond_state = mojom::BluetoothBondState::NONE; | 1710 mojom::BluetoothBondState bond_state = mojom::BluetoothBondState::NONE; |
1698 if (device && device->IsPaired()) { | 1711 if (device && device->IsPaired()) { |
1699 bond_state = mojom::BluetoothBondState::BONDED; | 1712 bond_state = mojom::BluetoothBondState::BONDED; |
1700 } | 1713 } |
1701 bluetooth_instance->OnBondStateChanged(mojom::BluetoothStatus::FAIL, | 1714 bluetooth_instance->OnBondStateChanged(mojom::BluetoothStatus::FAIL, |
1702 std::move(addr), bond_state); | 1715 std::move(addr), bond_state); |
1703 } | 1716 } |
1704 | 1717 |
1705 mojo::Array<mojom::BluetoothPropertyPtr> | 1718 mojo::Array<mojom::BluetoothPropertyPtr> |
1706 ArcBluetoothBridge::GetDeviceProperties(mojom::BluetoothPropertyType type, | 1719 ArcBluetoothBridge::GetDeviceProperties(mojom::BluetoothPropertyType type, |
1707 BluetoothDevice* device) const { | 1720 const BluetoothDevice* device) const { |
1708 mojo::Array<mojom::BluetoothPropertyPtr> properties; | 1721 mojo::Array<mojom::BluetoothPropertyPtr> properties; |
1709 | 1722 |
1710 if (!device) { | 1723 if (!device) { |
1711 return properties; | 1724 return properties; |
1712 } | 1725 } |
1713 | 1726 |
1714 if (type == mojom::BluetoothPropertyType::ALL || | 1727 if (type == mojom::BluetoothPropertyType::ALL || |
1715 type == mojom::BluetoothPropertyType::BDNAME) { | 1728 type == mojom::BluetoothPropertyType::BDNAME) { |
1716 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); | 1729 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); |
1717 btp->set_bdname(device->GetName() ? device->GetName().value() : nullptr); | 1730 btp->set_bdname(device->GetName() ? device->GetName().value() : nullptr); |
1718 properties.push_back(std::move(btp)); | 1731 properties.push_back(std::move(btp)); |
1719 } | 1732 } |
1720 if (type == mojom::BluetoothPropertyType::ALL || | 1733 if (type == mojom::BluetoothPropertyType::ALL || |
1721 type == mojom::BluetoothPropertyType::BDADDR) { | 1734 type == mojom::BluetoothPropertyType::BDADDR) { |
1722 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); | 1735 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); |
1723 btp->set_bdaddr(mojom::BluetoothAddress::From(device->GetAddress())); | 1736 btp->set_bdaddr(mojom::BluetoothAddress::From(device->GetAddress())); |
1724 properties.push_back(std::move(btp)); | 1737 properties.push_back(std::move(btp)); |
1725 } | 1738 } |
1726 if (type == mojom::BluetoothPropertyType::ALL || | 1739 if (type == mojom::BluetoothPropertyType::ALL || |
1727 type == mojom::BluetoothPropertyType::UUIDS) { | 1740 type == mojom::BluetoothPropertyType::UUIDS) { |
1728 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); | |
1729 BluetoothDevice::UUIDSet uuids = device->GetUUIDs(); | 1741 BluetoothDevice::UUIDSet uuids = device->GetUUIDs(); |
1730 btp->set_uuids(std::vector<BluetoothUUID>(uuids.begin(), uuids.end())); | 1742 if (uuids.size() > 0) { |
1731 properties.push_back(std::move(btp)); | 1743 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); |
1744 btp->set_uuids(std::vector<BluetoothUUID>(uuids.begin(), uuids.end())); | |
1745 properties.push_back(std::move(btp)); | |
1746 } | |
1732 } | 1747 } |
1733 if (type == mojom::BluetoothPropertyType::ALL || | 1748 if (type == mojom::BluetoothPropertyType::ALL || |
1734 type == mojom::BluetoothPropertyType::CLASS_OF_DEVICE) { | 1749 type == mojom::BluetoothPropertyType::CLASS_OF_DEVICE) { |
1735 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); | 1750 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); |
1736 btp->set_device_class(device->GetBluetoothClass()); | 1751 btp->set_device_class(device->GetBluetoothClass()); |
1737 properties.push_back(std::move(btp)); | 1752 properties.push_back(std::move(btp)); |
1738 } | 1753 } |
1739 if (type == mojom::BluetoothPropertyType::ALL || | 1754 if (type == mojom::BluetoothPropertyType::ALL || |
1740 type == mojom::BluetoothPropertyType::TYPE_OF_DEVICE) { | 1755 type == mojom::BluetoothPropertyType::TYPE_OF_DEVICE) { |
1741 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); | 1756 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); |
1742 btp->set_device_type(device->GetType()); | 1757 btp->set_device_type(device->GetType()); |
1743 properties.push_back(std::move(btp)); | 1758 properties.push_back(std::move(btp)); |
1744 } | 1759 } |
1745 if (type == mojom::BluetoothPropertyType::ALL || | 1760 if (type == mojom::BluetoothPropertyType::ALL || |
1746 type == mojom::BluetoothPropertyType::REMOTE_FRIENDLY_NAME) { | 1761 type == mojom::BluetoothPropertyType::REMOTE_FRIENDLY_NAME) { |
1747 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); | 1762 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); |
1748 btp->set_remote_friendly_name( | 1763 btp->set_remote_friendly_name( |
1749 mojo::String::From(base::UTF16ToUTF8(device->GetNameForDisplay()))); | 1764 mojo::String::From(base::UTF16ToUTF8(device->GetNameForDisplay()))); |
1750 properties.push_back(std::move(btp)); | 1765 properties.push_back(std::move(btp)); |
1751 } | 1766 } |
1752 if (type == mojom::BluetoothPropertyType::ALL || | 1767 if (type == mojom::BluetoothPropertyType::ALL || |
1753 type == mojom::BluetoothPropertyType::REMOTE_RSSI) { | 1768 type == mojom::BluetoothPropertyType::REMOTE_RSSI) { |
1754 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); | |
1755 base::Optional<int8_t> rssi = device->GetInquiryRSSI(); | 1769 base::Optional<int8_t> rssi = device->GetInquiryRSSI(); |
1756 btp->set_remote_rssi(rssi.value_or(mojom::kUnknownPower)); | 1770 if (rssi.has_value()) { |
1757 properties.push_back(std::move(btp)); | 1771 mojom::BluetoothPropertyPtr btp = mojom::BluetoothProperty::New(); |
1772 btp->set_remote_rssi(rssi.value()); | |
1773 properties.push_back(std::move(btp)); | |
1774 } | |
1758 } | 1775 } |
1759 // TODO(smbarber): Add remote version info | 1776 // TODO(smbarber): Add remote version info |
1760 | 1777 |
1761 return properties; | 1778 return properties; |
1762 } | 1779 } |
1763 | 1780 |
1764 mojo::Array<mojom::BluetoothPropertyPtr> | 1781 mojo::Array<mojom::BluetoothPropertyPtr> |
1765 ArcBluetoothBridge::GetAdapterProperties( | 1782 ArcBluetoothBridge::GetAdapterProperties( |
1766 mojom::BluetoothPropertyType type) const { | 1783 mojom::BluetoothPropertyType type) const { |
1767 mojo::Array<mojom::BluetoothPropertyPtr> properties; | 1784 mojo::Array<mojom::BluetoothPropertyPtr> properties; |
(...skipping 93 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1861 return properties; | 1878 return properties; |
1862 } | 1879 } |
1863 | 1880 |
1864 // Android support 5 types of Advertising Data. | 1881 // Android support 5 types of Advertising Data. |
1865 // However Chrome didn't expose AdvertiseFlag and ManufacturerData. | 1882 // However Chrome didn't expose AdvertiseFlag and ManufacturerData. |
1866 // So we will only expose local_name, service_uuids and service_data. | 1883 // So we will only expose local_name, service_uuids and service_data. |
1867 // Note that we need to use UUID 16 bits because Android does not support | 1884 // Note that we need to use UUID 16 bits because Android does not support |
1868 // UUID 128 bits. | 1885 // UUID 128 bits. |
1869 // TODO(crbug.com/618442) Make Chrome expose missing data. | 1886 // TODO(crbug.com/618442) Make Chrome expose missing data. |
1870 mojo::Array<mojom::BluetoothAdvertisingDataPtr> | 1887 mojo::Array<mojom::BluetoothAdvertisingDataPtr> |
1871 ArcBluetoothBridge::GetAdvertisingData(BluetoothDevice* device) const { | 1888 ArcBluetoothBridge::GetAdvertisingData(const BluetoothDevice* device) const { |
1872 mojo::Array<mojom::BluetoothAdvertisingDataPtr> advertising_data; | 1889 mojo::Array<mojom::BluetoothAdvertisingDataPtr> advertising_data; |
1873 | 1890 |
1874 // LocalName | 1891 // LocalName |
1875 mojom::BluetoothAdvertisingDataPtr local_name = | 1892 mojom::BluetoothAdvertisingDataPtr local_name = |
1876 mojom::BluetoothAdvertisingData::New(); | 1893 mojom::BluetoothAdvertisingData::New(); |
1877 local_name->set_local_name(device->GetName() ? device->GetName().value() | 1894 local_name->set_local_name(device->GetName() ? device->GetName().value() |
1878 : nullptr); | 1895 : nullptr); |
1879 advertising_data.push_back(std::move(local_name)); | 1896 advertising_data.push_back(std::move(local_name)); |
1880 | 1897 |
1881 // ServiceUuid | 1898 // ServiceUuid |
(...skipping 27 matching lines...) Expand all Loading... | |
1909 service_data->data.Swap(&data_copy); | 1926 service_data->data.Swap(&data_copy); |
1910 | 1927 |
1911 service_data_element->set_service_data(std::move(service_data)); | 1928 service_data_element->set_service_data(std::move(service_data)); |
1912 advertising_data.push_back(std::move(service_data_element)); | 1929 advertising_data.push_back(std::move(service_data_element)); |
1913 } | 1930 } |
1914 | 1931 |
1915 return advertising_data; | 1932 return advertising_data; |
1916 } | 1933 } |
1917 | 1934 |
1918 void ArcBluetoothBridge::SendCachedDevicesFound() const { | 1935 void ArcBluetoothBridge::SendCachedDevicesFound() const { |
1936 DCHECK(bluetooth_adapter_); | |
1937 | |
1919 // Send devices that have already been discovered, but aren't connected. | 1938 // Send devices that have already been discovered, but aren't connected. |
1920 auto* bluetooth_instance = | |
1921 arc_bridge_service()->bluetooth()->GetInstanceForMethod("OnDeviceFound"); | |
1922 if (!bluetooth_instance) | |
1923 return; | |
1924 auto* btle_instance = arc_bridge_service()->bluetooth()->GetInstanceForMethod( | |
1925 "OnLEDeviceFound", kMinBtleVersion); | |
1926 | |
1927 BluetoothAdapter::DeviceList devices = bluetooth_adapter_->GetDevices(); | 1939 BluetoothAdapter::DeviceList devices = bluetooth_adapter_->GetDevices(); |
1928 for (auto* device : devices) { | 1940 for (auto* device : devices) { |
1929 if (device->IsPaired()) | 1941 if (device->IsPaired()) |
1930 continue; | 1942 continue; |
1931 | 1943 |
1932 mojo::Array<mojom::BluetoothPropertyPtr> properties = | 1944 SendDeviceData(device); |
1933 GetDeviceProperties(mojom::BluetoothPropertyType::ALL, device); | |
1934 | |
1935 bluetooth_instance->OnDeviceFound(std::move(properties)); | |
1936 | |
1937 if (btle_instance) { | |
1938 mojom::BluetoothAddressPtr addr = | |
1939 mojom::BluetoothAddress::From(device->GetAddress()); | |
1940 base::Optional<int8_t> rssi = device->GetInquiryRSSI(); | |
1941 mojo::Array<mojom::BluetoothAdvertisingDataPtr> adv_data = | |
1942 GetAdvertisingData(device); | |
1943 btle_instance->OnLEDeviceFound(std::move(addr), | |
1944 rssi.value_or(mojom::kUnknownPower), | |
1945 std::move(adv_data)); | |
1946 } | |
1947 } | 1945 } |
1948 } | 1946 } |
1949 | 1947 |
1950 void ArcBluetoothBridge::SendCachedPairedDevices() const { | 1948 void ArcBluetoothBridge::SendCachedPairedDevices() const { |
1951 DCHECK(bluetooth_adapter_); | 1949 DCHECK(bluetooth_adapter_); |
1952 auto* bluetooth_instance = | |
1953 arc_bridge_service()->bluetooth()->GetInstanceForMethod("OnDeviceFound"); | |
1954 if (!bluetooth_instance) | |
1955 return; | |
1956 auto* btle_instance = arc_bridge_service()->bluetooth()->GetInstanceForMethod( | |
1957 "OnLEDeviceFound", kMinBtleVersion); | |
1958 | 1950 |
1959 BluetoothAdapter::DeviceList devices = bluetooth_adapter_->GetDevices(); | 1951 BluetoothAdapter::DeviceList devices = bluetooth_adapter_->GetDevices(); |
1960 for (auto* device : devices) { | 1952 for (auto* device : devices) { |
1961 if (!device->IsPaired()) | 1953 if (!device->IsPaired()) |
1962 continue; | 1954 continue; |
1963 | 1955 |
1964 mojo::Array<mojom::BluetoothPropertyPtr> properties = | 1956 SendDeviceData(device); |
1965 GetDeviceProperties(mojom::BluetoothPropertyType::ALL, device); | |
1966 | |
1967 bluetooth_instance->OnDeviceFound(std::move(properties)); | |
1968 | |
1969 mojom::BluetoothAddressPtr addr = | |
1970 mojom::BluetoothAddress::From(device->GetAddress()); | |
1971 | |
1972 if (btle_instance) { | |
1973 base::Optional<int8_t> rssi = device->GetInquiryRSSI(); | |
1974 mojo::Array<mojom::BluetoothAdvertisingDataPtr> adv_data = | |
1975 GetAdvertisingData(device); | |
1976 btle_instance->OnLEDeviceFound(addr->Clone(), | |
1977 rssi.value_or(mojom::kUnknownPower), | |
1978 std::move(adv_data)); | |
1979 } | |
1980 | 1957 |
1981 // OnBondStateChanged must be called with mojom::BluetoothBondState::BONDING | 1958 // OnBondStateChanged must be called with mojom::BluetoothBondState::BONDING |
1982 // to make sure the bond state machine on Android is ready to take the | 1959 // to make sure the bond state machine on Android is ready to take the |
1983 // pair-done event. Otherwise the pair-done event will be dropped as an | 1960 // pair-done event. Otherwise the pair-done event will be dropped as an |
1984 // invalid change of paired status. | 1961 // invalid change of paired status. |
1962 mojom::BluetoothAddressPtr addr = | |
1963 mojom::BluetoothAddress::From(device->GetAddress()); | |
1985 OnPairing(addr->Clone()); | 1964 OnPairing(addr->Clone()); |
1986 OnPairedDone(std::move(addr)); | 1965 OnPairedDone(std::move(addr)); |
1987 } | 1966 } |
1988 } | 1967 } |
1989 | 1968 |
1990 void ArcBluetoothBridge::OnGetServiceRecordsDone( | 1969 void ArcBluetoothBridge::OnGetServiceRecordsDone( |
1991 mojom::BluetoothAddressPtr remote_addr, | 1970 mojom::BluetoothAddressPtr remote_addr, |
1992 const BluetoothUUID& target_uuid, | 1971 const BluetoothUUID& target_uuid, |
1993 const std::vector<bluez::BluetoothServiceRecordBlueZ>& records_bluez) { | 1972 const std::vector<bluez::BluetoothServiceRecordBlueZ>& records_bluez) { |
1994 auto* sdp_bluetooth_instance = | 1973 auto* sdp_bluetooth_instance = |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
2030 sdp_bluetooth_instance->OnGetSdpRecords( | 2009 sdp_bluetooth_instance->OnGetSdpRecords( |
2031 status, std::move(remote_addr), target_uuid, | 2010 status, std::move(remote_addr), target_uuid, |
2032 mojo::Array<mojom::BluetoothSdpRecordPtr>::New(0)); | 2011 mojo::Array<mojom::BluetoothSdpRecordPtr>::New(0)); |
2033 } | 2012 } |
2034 | 2013 |
2035 bool ArcBluetoothBridge::CalledOnValidThread() { | 2014 bool ArcBluetoothBridge::CalledOnValidThread() { |
2036 return thread_checker_.CalledOnValidThread(); | 2015 return thread_checker_.CalledOnValidThread(); |
2037 } | 2016 } |
2038 | 2017 |
2039 } // namespace arc | 2018 } // namespace arc |
OLD | NEW |