// Copyright (c) 2012 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. #include "net/android/network_change_notifier_delegate_android.h" #include "base/android/build_info.h" #include "base/android/jni_array.h" #include "base/check.h" #include "base/notreached.h" #include "net/android/network_change_notifier_android.h" #include "net/net_jni_headers/NetworkActiveNotifier_jni.h" #include "net/net_jni_headers/NetworkChangeNotifier_jni.h" using base::android::JavaParamRef; using base::android::JavaRef; using base::android::ScopedJavaLocalRef; namespace net { namespace { // Converts a Java side connection type (integer) to // the native side NetworkChangeNotifier::ConnectionType. NetworkChangeNotifier::ConnectionType ConvertConnectionType( jint connection_type) { switch (connection_type) { case NetworkChangeNotifier::CONNECTION_UNKNOWN: case NetworkChangeNotifier::CONNECTION_ETHERNET: case NetworkChangeNotifier::CONNECTION_WIFI: case NetworkChangeNotifier::CONNECTION_2G: case NetworkChangeNotifier::CONNECTION_3G: case NetworkChangeNotifier::CONNECTION_4G: case NetworkChangeNotifier::CONNECTION_5G: case NetworkChangeNotifier::CONNECTION_NONE: case NetworkChangeNotifier::CONNECTION_BLUETOOTH: break; default: NOTREACHED() << "Unknown connection type received: " << connection_type; return NetworkChangeNotifier::CONNECTION_UNKNOWN; } return static_cast(connection_type); } // Converts a Java side connection cost (integer) to // the native side NetworkChangeNotifier::ConnectionCost. NetworkChangeNotifier::ConnectionCost ConvertConnectionCost( jint connection_cost) { switch (connection_cost) { case NetworkChangeNotifier::CONNECTION_COST_UNKNOWN: case NetworkChangeNotifier::CONNECTION_COST_UNMETERED: case NetworkChangeNotifier::CONNECTION_COST_METERED: break; default: NOTREACHED() << "Unknown connection cost received: " << connection_cost; return NetworkChangeNotifier::CONNECTION_COST_UNKNOWN; } return static_cast(connection_cost); } // Converts a Java side connection type (integer) to // the native side NetworkChangeNotifier::ConnectionType. NetworkChangeNotifier::ConnectionSubtype ConvertConnectionSubtype( jint subtype) { DCHECK(subtype >= 0 && subtype <= NetworkChangeNotifier::SUBTYPE_LAST); return static_cast(subtype); } } // namespace // static void NetworkChangeNotifierDelegateAndroid::JavaLongArrayToNetworkMap( JNIEnv* env, const JavaRef& long_array, NetworkMap* network_map) { std::vector int64_list; base::android::JavaLongArrayToInt64Vector(env, long_array, &int64_list); network_map->clear(); for (auto i = int64_list.begin(); i != int64_list.end(); ++i) { NetworkChangeNotifier::NetworkHandle network_handle = *i; CHECK(++i != int64_list.end()); (*network_map)[network_handle] = static_cast(*i); } } NetworkChangeNotifierDelegateAndroid::NetworkChangeNotifierDelegateAndroid() : java_network_change_notifier_(Java_NetworkChangeNotifier_init( base::android::AttachCurrentThread())), register_network_callback_failed_( Java_NetworkChangeNotifier_registerNetworkCallbackFailed( base::android::AttachCurrentThread(), java_network_change_notifier_)), // TODO(crbug.com/1042122): Remove once Cronet drops Kitkat support. is_default_network_active_api_supported_( base::android::BuildInfo::GetInstance()->sdk_int() >= base::android::SDK_VERSION_LOLLIPOP), default_network_active_observers_(0) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_addNativeObserver( env, java_network_change_notifier_, reinterpret_cast(this)); SetCurrentConnectionType( ConvertConnectionType(Java_NetworkChangeNotifier_getCurrentConnectionType( env, java_network_change_notifier_))); SetCurrentConnectionCost( ConvertConnectionCost(Java_NetworkChangeNotifier_getCurrentConnectionCost( env, java_network_change_notifier_))); SetCurrentMaxBandwidth( NetworkChangeNotifierAndroid::GetMaxBandwidthMbpsForConnectionSubtype( GetCurrentConnectionSubtype())); SetCurrentDefaultNetwork(Java_NetworkChangeNotifier_getCurrentDefaultNetId( env, java_network_change_notifier_)); NetworkMap network_map; ScopedJavaLocalRef networks_and_types = Java_NetworkChangeNotifier_getCurrentNetworksAndTypes( env, java_network_change_notifier_); JavaLongArrayToNetworkMap(env, networks_and_types, &network_map); SetCurrentNetworksAndTypes(network_map); if (is_default_network_active_api_supported_) java_network_active_notifier_ = Java_NetworkActiveNotifier_build( base::android::AttachCurrentThread(), reinterpret_cast(this)); } NetworkChangeNotifierDelegateAndroid::~NetworkChangeNotifierDelegateAndroid() { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); DCHECK_EQ(default_network_active_observers_, 0); { base::AutoLock auto_lock(observer_lock_); DCHECK(!observer_); } JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_removeNativeObserver( env, java_network_change_notifier_, reinterpret_cast(this)); } NetworkChangeNotifier::ConnectionType NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionType() const { base::AutoLock auto_lock(connection_lock_); return connection_type_; } NetworkChangeNotifier::ConnectionCost NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionCost() { base::AutoLock auto_lock(connection_lock_); return connection_cost_; } NetworkChangeNotifier::ConnectionSubtype NetworkChangeNotifierDelegateAndroid::GetCurrentConnectionSubtype() const { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); return ConvertConnectionSubtype( Java_NetworkChangeNotifier_getCurrentConnectionSubtype( base::android::AttachCurrentThread(), java_network_change_notifier_)); } void NetworkChangeNotifierDelegateAndroid:: GetCurrentMaxBandwidthAndConnectionType( double* max_bandwidth_mbps, ConnectionType* connection_type) const { base::AutoLock auto_lock(connection_lock_); *connection_type = connection_type_; *max_bandwidth_mbps = connection_max_bandwidth_; } NetworkChangeNotifier::ConnectionType NetworkChangeNotifierDelegateAndroid::GetNetworkConnectionType( NetworkChangeNotifier::NetworkHandle network) const { base::AutoLock auto_lock(connection_lock_); auto network_entry = network_map_.find(network); if (network_entry == network_map_.end()) return ConnectionType::CONNECTION_UNKNOWN; return network_entry->second; } NetworkChangeNotifier::NetworkHandle NetworkChangeNotifierDelegateAndroid::GetCurrentDefaultNetwork() const { base::AutoLock auto_lock(connection_lock_); return default_network_; } void NetworkChangeNotifierDelegateAndroid::GetCurrentlyConnectedNetworks( NetworkList* network_list) const { network_list->clear(); base::AutoLock auto_lock(connection_lock_); for (auto i : network_map_) network_list->push_back(i.first); } bool NetworkChangeNotifierDelegateAndroid::IsDefaultNetworkActive() { // If the API is not available always return true to avoid indefinitely // batching. if (!is_default_network_active_api_supported_) return true; JNIEnv* env = base::android::AttachCurrentThread(); return Java_NetworkActiveNotifier_isDefaultNetworkActive( env, java_network_active_notifier_); } void NetworkChangeNotifierDelegateAndroid::NotifyConnectionCostChanged( JNIEnv* env, const JavaParamRef& obj, jint new_connection_cost) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); const ConnectionCost actual_connection_cost = ConvertConnectionCost(new_connection_cost); SetCurrentConnectionCost(actual_connection_cost); base::AutoLock auto_lock(observer_lock_); if (observer_) observer_->OnConnectionCostChanged(); } void NetworkChangeNotifierDelegateAndroid::NotifyConnectionTypeChanged( JNIEnv* env, const JavaParamRef& obj, jint new_connection_type, jlong default_netid) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); const ConnectionType actual_connection_type = ConvertConnectionType( new_connection_type); SetCurrentConnectionType(actual_connection_type); NetworkHandle default_network = default_netid; if (default_network != GetCurrentDefaultNetwork()) { SetCurrentDefaultNetwork(default_network); bool default_exists; { base::AutoLock auto_lock(connection_lock_); // |default_network| may be an invalid value (i.e. -1) in cases where // the device is disconnected or when run on Android versions prior to L, // in which case |default_exists| will correctly be false and no // OnNetworkMadeDefault notification will be sent. default_exists = network_map_.find(default_network) != network_map_.end(); } // Android Lollipop had race conditions where CONNECTIVITY_ACTION intents // were sent out before the network was actually made the default. // Delay sending the OnNetworkMadeDefault notification until we are // actually notified that the network connected in NotifyOfNetworkConnect. if (default_exists) { base::AutoLock auto_lock(observer_lock_); if (observer_) observer_->OnNetworkMadeDefault(default_network); } } base::AutoLock auto_lock(observer_lock_); if (observer_) observer_->OnConnectionTypeChanged(); } jint NetworkChangeNotifierDelegateAndroid::GetConnectionType(JNIEnv*, jobject) const { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); return GetCurrentConnectionType(); } jint NetworkChangeNotifierDelegateAndroid::GetConnectionCost(JNIEnv*, jobject) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); return GetCurrentConnectionCost(); } void NetworkChangeNotifierDelegateAndroid::NotifyMaxBandwidthChanged( JNIEnv* env, const JavaParamRef& obj, jint subtype) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); double new_max_bandwidth = NetworkChangeNotifierAndroid::GetMaxBandwidthMbpsForConnectionSubtype( ConvertConnectionSubtype(subtype)); SetCurrentMaxBandwidth(new_max_bandwidth); const ConnectionType connection_type = GetCurrentConnectionType(); base::AutoLock auto_lock(observer_lock_); if (observer_) { observer_->OnMaxBandwidthChanged(new_max_bandwidth, connection_type); } } void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkConnect( JNIEnv* env, const JavaParamRef& obj, jlong net_id, jint connection_type) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); NetworkHandle network = net_id; bool already_exists; bool is_default_network; { base::AutoLock auto_lock(connection_lock_); already_exists = network_map_.find(network) != network_map_.end(); network_map_[network] = static_cast(connection_type); is_default_network = (network == default_network_); } // Android Lollipop would send many duplicate notifications. // This was later fixed in Android Marshmallow. // Deduplicate them here by avoiding sending duplicate notifications. if (!already_exists) { base::AutoLock auto_lock(observer_lock_); if (observer_) { observer_->OnNetworkConnected(network); if (is_default_network) observer_->OnNetworkMadeDefault(network); } } } void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkSoonToDisconnect( JNIEnv* env, const JavaParamRef& obj, jlong net_id) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); NetworkHandle network = net_id; { base::AutoLock auto_lock(connection_lock_); if (network_map_.find(network) == network_map_.end()) return; } base::AutoLock auto_lock(observer_lock_); if (observer_) observer_->OnNetworkSoonToDisconnect(network); } void NetworkChangeNotifierDelegateAndroid::NotifyOfNetworkDisconnect( JNIEnv* env, const JavaParamRef& obj, jlong net_id) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); NetworkHandle network = net_id; { base::AutoLock auto_lock(connection_lock_); if (network == default_network_) default_network_ = NetworkChangeNotifier::kInvalidNetworkHandle; if (network_map_.erase(network) == 0) return; } base::AutoLock auto_lock(observer_lock_); if (observer_) observer_->OnNetworkDisconnected(network); } void NetworkChangeNotifierDelegateAndroid::NotifyPurgeActiveNetworkList( JNIEnv* env, const JavaParamRef& obj, const JavaParamRef& active_networks) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); NetworkList active_network_list; base::android::JavaLongArrayToInt64Vector(env, active_networks, &active_network_list); NetworkList disconnected_networks; { base::AutoLock auto_lock(connection_lock_); for (auto i : network_map_) { bool found = false; for (auto j : active_network_list) { if (j == i.first) { found = true; break; } } if (!found) { disconnected_networks.push_back(i.first); } } } for (auto disconnected_network : disconnected_networks) NotifyOfNetworkDisconnect(env, obj, disconnected_network); } void NetworkChangeNotifierDelegateAndroid::NotifyOfDefaultNetworkActive( JNIEnv* env) { base::AutoLock auto_lock(observer_lock_); if (observer_) observer_->OnDefaultNetworkActive(); } void NetworkChangeNotifierDelegateAndroid::RegisterObserver( Observer* observer) { base::AutoLock auto_lock(observer_lock_); DCHECK(!observer_); observer_ = observer; } void NetworkChangeNotifierDelegateAndroid::UnregisterObserver( Observer* observer) { base::AutoLock auto_lock(observer_lock_); DCHECK_EQ(observer_, observer); observer_ = nullptr; } void NetworkChangeNotifierDelegateAndroid::DefaultNetworkActiveObserverAdded() { if (!is_default_network_active_api_supported_) return; if (default_network_active_observers_.fetch_add(1) == 0) EnableDefaultNetworkActiveNotifications(); } void NetworkChangeNotifierDelegateAndroid:: DefaultNetworkActiveObserverRemoved() { if (!is_default_network_active_api_supported_) return; if (default_network_active_observers_.fetch_sub(1) == 1) DisableDefaultNetworkActiveNotifications(); } void NetworkChangeNotifierDelegateAndroid:: EnableDefaultNetworkActiveNotifications() { if (!is_default_network_active_api_supported_) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkActiveNotifier_enableNotifications(env, java_network_active_notifier_); } void NetworkChangeNotifierDelegateAndroid:: DisableDefaultNetworkActiveNotifications() { if (!is_default_network_active_api_supported_) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkActiveNotifier_disableNotifications( env, java_network_active_notifier_); } void NetworkChangeNotifierDelegateAndroid::SetCurrentConnectionType( ConnectionType new_connection_type) { base::AutoLock auto_lock(connection_lock_); connection_type_ = new_connection_type; } void NetworkChangeNotifierDelegateAndroid::SetCurrentConnectionCost( ConnectionCost new_connection_cost) { base::AutoLock auto_lock(connection_lock_); connection_cost_ = new_connection_cost; } void NetworkChangeNotifierDelegateAndroid::SetCurrentMaxBandwidth( double max_bandwidth) { base::AutoLock auto_lock(connection_lock_); connection_max_bandwidth_ = max_bandwidth; } void NetworkChangeNotifierDelegateAndroid::SetCurrentDefaultNetwork( NetworkHandle default_network) { base::AutoLock auto_lock(connection_lock_); default_network_ = default_network; } void NetworkChangeNotifierDelegateAndroid::SetCurrentNetworksAndTypes( NetworkMap network_map) { base::AutoLock auto_lock(connection_lock_); network_map_ = network_map; } void NetworkChangeNotifierDelegateAndroid::SetOnline() { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_forceConnectivityState(env, true); } void NetworkChangeNotifierDelegateAndroid::SetOffline() { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_forceConnectivityState(env, false); } void NetworkChangeNotifierDelegateAndroid::FakeNetworkConnected( NetworkChangeNotifier::NetworkHandle network, ConnectionType type) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_fakeNetworkConnected(env, network, type); } void NetworkChangeNotifierDelegateAndroid::FakeNetworkSoonToBeDisconnected( NetworkChangeNotifier::NetworkHandle network) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_fakeNetworkSoonToBeDisconnected(env, network); } void NetworkChangeNotifierDelegateAndroid::FakeNetworkDisconnected( NetworkChangeNotifier::NetworkHandle network) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_fakeNetworkDisconnected(env, network); } void NetworkChangeNotifierDelegateAndroid::FakePurgeActiveNetworkList( NetworkChangeNotifier::NetworkList networks) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_fakePurgeActiveNetworkList( env, base::android::ToJavaLongArray(env, networks)); } void NetworkChangeNotifierDelegateAndroid::FakeDefaultNetwork( NetworkChangeNotifier::NetworkHandle network, ConnectionType type) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_fakeDefaultNetwork(env, network, type); } void NetworkChangeNotifierDelegateAndroid::FakeConnectionCostChanged( ConnectionCost cost) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_fakeConnectionCostChanged(env, cost); } void NetworkChangeNotifierDelegateAndroid::FakeConnectionSubtypeChanged( ConnectionSubtype subtype) { JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkChangeNotifier_fakeConnectionSubtypeChanged(env, subtype); } void NetworkChangeNotifierDelegateAndroid::FakeDefaultNetworkActive() { if (!is_default_network_active_api_supported_) return; JNIEnv* env = base::android::AttachCurrentThread(); Java_NetworkActiveNotifier_fakeDefaultNetworkActive( env, java_network_active_notifier_); } } // namespace net