blob: 74b4160a50aa0cff1285aac780cf7e3d300a133c [file] [log] [blame]
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bluetooth.pan;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.NetworkAgent;
import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkFactory;
import android.net.ip.IIpClient;
import android.net.ip.IpClientUtil;
import android.net.ip.IpClientUtil.WaitForProvisioningCallbacks;
import android.net.shared.ProvisioningConfiguration;
import android.os.Looper;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
/**
* This class tracks the data connection associated with Bluetooth
* reverse tethering. PanService calls it when a reverse tethered
* connection needs to be activated or deactivated.
*
* @hide
*/
public class BluetoothTetheringNetworkFactory extends NetworkFactory {
private static final String NETWORK_TYPE = "Bluetooth Tethering";
private static final String TAG = "BluetoothTetheringNetworkFactory";
private static final int NETWORK_SCORE = 69;
private final NetworkCapabilities mNetworkCapabilities;
private final Context mContext;
private final PanService mPanService;
// All accesses to these must be synchronized(this).
private IIpClient mIpClient;
@GuardedBy("this")
private int mIpClientStartIndex = 0;
private String mInterfaceName;
private NetworkAgent mNetworkAgent;
public BluetoothTetheringNetworkFactory(Context context, Looper looper, PanService panService) {
super(looper, context, NETWORK_TYPE, new NetworkCapabilities());
mContext = context;
mPanService = panService;
mNetworkCapabilities = new NetworkCapabilities();
initNetworkCapabilities();
setCapabilityFilter(mNetworkCapabilities);
}
private class BtIpClientCallback extends WaitForProvisioningCallbacks {
private final int mCurrentStartIndex;
private BtIpClientCallback(int currentStartIndex) {
mCurrentStartIndex = currentStartIndex;
}
@Override
public void onIpClientCreated(IIpClient ipClient) {
synchronized (BluetoothTetheringNetworkFactory.this) {
if (mCurrentStartIndex != mIpClientStartIndex) {
// Do not start IpClient: the current request is obsolete.
// IpClient will be GCed eventually as the IIpClient Binder token goes out
// of scope.
return;
}
mIpClient = ipClient;
try {
mIpClient.startProvisioning(new ProvisioningConfiguration.Builder()
.withoutMultinetworkPolicyTracker()
.withoutIpReachabilityMonitor()
.build().toStableParcelable());
} catch (RemoteException e) {
Log.e(TAG, "Error starting IpClient provisioning", e);
}
}
}
@Override
public void onLinkPropertiesChange(LinkProperties newLp) {
synchronized (BluetoothTetheringNetworkFactory.this) {
if (mNetworkAgent != null) {
mNetworkAgent.sendLinkProperties(newLp);
}
}
}
}
private void stopIpClientLocked() {
// Mark all previous start requests as obsolete
mIpClientStartIndex++;
if (mIpClient != null) {
try {
mIpClient.shutdown();
} catch (RemoteException e) {
Log.e(TAG, "Error shutting down IpClient", e);
}
mIpClient = null;
}
}
private BtIpClientCallback startIpClientLocked() {
mIpClientStartIndex++;
final BtIpClientCallback callback = new BtIpClientCallback(mIpClientStartIndex);
IpClientUtil.makeIpClient(mContext, mInterfaceName, callback);
return callback;
}
// Called by NetworkFactory when PanService and NetworkFactory both desire a Bluetooth
// reverse-tether connection. A network interface for Bluetooth reverse-tethering can be
// assumed to be available because we only register our NetworkFactory when it is so.
@Override
protected void startNetwork() {
// TODO: Figure out how to replace this thread with simple invocations
// of IpClient. This will likely necessitate a rethink about
// NetworkAgent and associated instance lifetimes.
Thread ipProvisioningThread = new Thread(new Runnable() {
@Override
public void run() {
final WaitForProvisioningCallbacks ipcCallback;
final int ipClientStartIndex;
synchronized (BluetoothTetheringNetworkFactory.this) {
if (TextUtils.isEmpty(mInterfaceName)) {
Log.e(TAG, "attempted to reverse tether without interface name");
return;
}
log("ipProvisioningThread(+" + mInterfaceName + ") start IP provisioning");
ipcCallback = startIpClientLocked();
ipClientStartIndex = mIpClientStartIndex;
}
final LinkProperties linkProperties = ipcCallback.waitForProvisioning();
if (linkProperties == null) {
Log.e(TAG, "IP provisioning error.");
synchronized (BluetoothTetheringNetworkFactory.this) {
stopIpClientLocked();
setScoreFilter(-1);
}
return;
}
final NetworkAgentConfig config = new NetworkAgentConfig.Builder()
.setLegacyType(ConnectivityManager.TYPE_BLUETOOTH)
.setLegacyTypeName(NETWORK_TYPE)
.build();
synchronized (BluetoothTetheringNetworkFactory.this) {
// Reverse tethering has been stopped, and stopping won the race : there is
// no point in creating the agent (and it would be leaked), so bail.
if (ipClientStartIndex != mIpClientStartIndex) return;
// Create our NetworkAgent.
mNetworkAgent =
new NetworkAgent(mContext, getLooper(), NETWORK_TYPE,
mNetworkCapabilities, linkProperties, NETWORK_SCORE,
config, getProvider()) {
@Override
public void unwanted() {
BluetoothTetheringNetworkFactory.this.onCancelRequest();
}
};
mNetworkAgent.register();
mNetworkAgent.markConnected();
}
}
});
ipProvisioningThread.start();
}
// Called from NetworkFactory to indicate ConnectivityService no longer desires a Bluetooth
// reverse-tether network.
@Override
protected void stopNetwork() {
// Let NetworkAgent disconnect do the teardown.
}
// Called by the NetworkFactory, NetworkAgent or PanService to tear down network.
private synchronized void onCancelRequest() {
stopIpClientLocked();
mInterfaceName = "";
if (mNetworkAgent != null) {
mNetworkAgent.unregister();
mNetworkAgent = null;
}
for (BluetoothDevice device : mPanService.getConnectedDevices()) {
mPanService.disconnect(device);
}
}
// Called by PanService when a network interface for Bluetooth reverse-tethering
// becomes available. We register our NetworkFactory at this point.
public void startReverseTether(final String iface) {
if (iface == null || TextUtils.isEmpty(iface)) {
Log.e(TAG, "attempted to reverse tether with empty interface");
return;
}
synchronized (this) {
if (!TextUtils.isEmpty(mInterfaceName)) {
Log.e(TAG, "attempted to reverse tether while already in process");
return;
}
mInterfaceName = iface;
// Advertise ourselves to ConnectivityService.
register();
setScoreFilter(NETWORK_SCORE);
}
}
// Called by PanService when a network interface for Bluetooth reverse-tethering
// goes away. We stop advertising ourselves to ConnectivityService at this point.
public synchronized void stopReverseTether() {
if (TextUtils.isEmpty(mInterfaceName)) {
Log.e(TAG, "attempted to stop reverse tether with nothing tethered");
return;
}
onCancelRequest();
setScoreFilter(-1);
terminate();
}
private void initNetworkCapabilities() {
mNetworkCapabilities.addTransportType(NetworkCapabilities.TRANSPORT_BLUETOOTH);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING);
mNetworkCapabilities.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED);
// Bluetooth v3 and v4 go up to 24 Mbps.
// TODO: Adjust this to actual connection bandwidth.
mNetworkCapabilities.setLinkUpstreamBandwidthKbps(24 * 1000);
mNetworkCapabilities.setLinkDownstreamBandwidthKbps(24 * 1000);
}
}