| /* |
| * 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); |
| } |
| } |