1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.bluetooth;
18
19import android.os.IBinder;
20import android.os.ServiceManager;
21import android.os.INetworkManagementService;
22import android.content.Context;
23import android.net.ConnectivityManager;
24import android.net.DhcpInfoInternal;
25import android.net.LinkCapabilities;
26import android.net.LinkProperties;
27import android.net.NetworkInfo;
28import android.net.NetworkInfo.DetailedState;
29import android.net.NetworkStateTracker;
30import android.net.NetworkUtils;
31import android.os.Handler;
32import android.os.Message;
33import android.util.Log;
34import java.net.InterfaceAddress;
35import android.net.LinkAddress;
36import android.net.RouteInfo;
37import java.net.Inet4Address;
38import android.os.SystemProperties;
39
40import java.util.concurrent.atomic.AtomicBoolean;
41import java.util.concurrent.atomic.AtomicInteger;
42
43/**
44 * This class tracks the data connection associated with Bluetooth
45 * reverse tethering. This is a singleton class and an instance will be
46 * created by ConnectivityService. BluetoothService will call into this
47 * when a reverse tethered connection needs to be activated.
48 *
49 * @hide
50 */
51public class BluetoothTetheringDataTracker implements NetworkStateTracker {
52    private static final String NETWORKTYPE = "BLUETOOTH_TETHER";
53    private static final String TAG = "BluetoothTethering";
54    private static final boolean DBG = true;
55    private static final boolean VDBG = false;
56
57    private AtomicBoolean mTeardownRequested = new AtomicBoolean(false);
58    private AtomicBoolean mPrivateDnsRouteSet = new AtomicBoolean(false);
59    private AtomicInteger mDefaultGatewayAddr = new AtomicInteger(0);
60    private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
61
62    private LinkProperties mLinkProperties;
63    private LinkCapabilities mLinkCapabilities;
64    private NetworkInfo mNetworkInfo;
65
66    private BluetoothPan mBluetoothPan;
67    private static String mIface;
68    private Thread mDhcpThread;
69    /* For sending events to connectivity service handler */
70    private Handler mCsHandler;
71    private Context mContext;
72    public static BluetoothTetheringDataTracker sInstance;
73
74    private BluetoothTetheringDataTracker() {
75        mNetworkInfo = new NetworkInfo(ConnectivityManager.TYPE_BLUETOOTH, 0, NETWORKTYPE, "");
76        mLinkProperties = new LinkProperties();
77        mLinkCapabilities = new LinkCapabilities();
78
79        mNetworkInfo.setIsAvailable(false);
80        setTeardownRequested(false);
81    }
82
83    public static synchronized BluetoothTetheringDataTracker getInstance() {
84        if (sInstance == null) sInstance = new BluetoothTetheringDataTracker();
85        return sInstance;
86    }
87
88    public Object Clone() throws CloneNotSupportedException {
89        throw new CloneNotSupportedException();
90    }
91
92    public void setTeardownRequested(boolean isRequested) {
93        mTeardownRequested.set(isRequested);
94    }
95
96    public boolean isTeardownRequested() {
97        return mTeardownRequested.get();
98    }
99
100    /**
101     * Begin monitoring connectivity
102     */
103    public void startMonitoring(Context context, Handler target) {
104        if (DBG) Log.d(TAG, "startMonitoring: target: " + target);
105        mContext = context;
106        mCsHandler = target;
107        if (VDBG) Log.d(TAG, "startMonitoring: mCsHandler: " + mCsHandler);
108        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
109        if (adapter != null) {
110            adapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.PAN);
111        }
112    }
113
114    private BluetoothProfile.ServiceListener mProfileServiceListener =
115        new BluetoothProfile.ServiceListener() {
116        public void onServiceConnected(int profile, BluetoothProfile proxy) {
117            mBluetoothPan = (BluetoothPan) proxy;
118        }
119        public void onServiceDisconnected(int profile) {
120            mBluetoothPan = null;
121        }
122    };
123
124    /**
125     * Disable connectivity to a network
126     * TODO: do away with return value after making MobileDataStateTracker async
127     */
128    public boolean teardown() {
129        mTeardownRequested.set(true);
130        if (mBluetoothPan != null) {
131            for (BluetoothDevice device: mBluetoothPan.getConnectedDevices()) {
132                mBluetoothPan.disconnect(device);
133            }
134        }
135        return true;
136    }
137
138    @Override
139    public void captivePortalCheckComplete() {
140        // not implemented
141    }
142
143    /**
144     * Re-enable connectivity to a network after a {@link #teardown()}.
145     */
146    public boolean reconnect() {
147        mTeardownRequested.set(false);
148        //Ignore
149        return true;
150    }
151
152    /**
153     * Turn the wireless radio off for a network.
154     * @param turnOn {@code true} to turn the radio on, {@code false}
155     */
156    public boolean setRadio(boolean turnOn) {
157        return true;
158    }
159
160    /**
161     * @return true - If are we currently tethered with another device.
162     */
163    public synchronized boolean isAvailable() {
164        return mNetworkInfo.isAvailable();
165    }
166
167    /**
168     * Tells the underlying networking system that the caller wants to
169     * begin using the named feature. The interpretation of {@code feature}
170     * is completely up to each networking implementation.
171     * @param feature the name of the feature to be used
172     * @param callingPid the process ID of the process that is issuing this request
173     * @param callingUid the user ID of the process that is issuing this request
174     * @return an integer value representing the outcome of the request.
175     * The interpretation of this value is specific to each networking
176     * implementation+feature combination, except that the value {@code -1}
177     * always indicates failure.
178     * TODO: needs to go away
179     */
180    public int startUsingNetworkFeature(String feature, int callingPid, int callingUid) {
181        return -1;
182    }
183
184    /**
185     * Tells the underlying networking system that the caller is finished
186     * using the named feature. The interpretation of {@code feature}
187     * is completely up to each networking implementation.
188     * @param feature the name of the feature that is no longer needed.
189     * @param callingPid the process ID of the process that is issuing this request
190     * @param callingUid the user ID of the process that is issuing this request
191     * @return an integer value representing the outcome of the request.
192     * The interpretation of this value is specific to each networking
193     * implementation+feature combination, except that the value {@code -1}
194     * always indicates failure.
195     * TODO: needs to go away
196     */
197    public int stopUsingNetworkFeature(String feature, int callingPid, int callingUid) {
198        return -1;
199    }
200
201    @Override
202    public void setUserDataEnable(boolean enabled) {
203        Log.w(TAG, "ignoring setUserDataEnable(" + enabled + ")");
204    }
205
206    @Override
207    public void setPolicyDataEnable(boolean enabled) {
208        Log.w(TAG, "ignoring setPolicyDataEnable(" + enabled + ")");
209    }
210
211    /**
212     * Check if private DNS route is set for the network
213     */
214    public boolean isPrivateDnsRouteSet() {
215        return mPrivateDnsRouteSet.get();
216    }
217
218    /**
219     * Set a flag indicating private DNS route is set
220     */
221    public void privateDnsRouteSet(boolean enabled) {
222        mPrivateDnsRouteSet.set(enabled);
223    }
224
225    /**
226     * Fetch NetworkInfo for the network
227     */
228    public synchronized NetworkInfo getNetworkInfo() {
229        return mNetworkInfo;
230    }
231
232    /**
233     * Fetch LinkProperties for the network
234     */
235    public synchronized LinkProperties getLinkProperties() {
236        return new LinkProperties(mLinkProperties);
237    }
238
239   /**
240     * A capability is an Integer/String pair, the capabilities
241     * are defined in the class LinkSocket#Key.
242     *
243     * @return a copy of this connections capabilities, may be empty but never null.
244     */
245    public LinkCapabilities getLinkCapabilities() {
246        return new LinkCapabilities(mLinkCapabilities);
247    }
248
249    /**
250     * Fetch default gateway address for the network
251     */
252    public int getDefaultGatewayAddr() {
253        return mDefaultGatewayAddr.get();
254    }
255
256    /**
257     * Check if default route is set
258     */
259    public boolean isDefaultRouteSet() {
260        return mDefaultRouteSet.get();
261    }
262
263    /**
264     * Set a flag indicating default route is set for the network
265     */
266    public void defaultRouteSet(boolean enabled) {
267        mDefaultRouteSet.set(enabled);
268    }
269
270    /**
271     * Return the system properties name associated with the tcp buffer sizes
272     * for this network.
273     */
274    public String getTcpBufferSizesPropName() {
275        return "net.tcp.buffersize.wifi";
276    }
277
278    private static short countPrefixLength(byte [] mask) {
279        short count = 0;
280        for (byte b : mask) {
281            for (int i = 0; i < 8; ++i) {
282                if ((b & (1 << i)) != 0) {
283                    ++count;
284                }
285            }
286        }
287        return count;
288    }
289
290
291    private boolean readLinkProperty(String iface) {
292        String DhcpPrefix = "dhcp." + iface + ".";
293        String ip = SystemProperties.get(DhcpPrefix + "ipaddress");
294        String dns1 = SystemProperties.get(DhcpPrefix + "dns1");
295        String dns2 = SystemProperties.get(DhcpPrefix + "dns2");
296        String gateway = SystemProperties.get(DhcpPrefix + "gateway");
297        String mask = SystemProperties.get(DhcpPrefix + "mask");
298        if(ip.isEmpty() || gateway.isEmpty()) {
299            Log.e(TAG, "readLinkProperty, ip: " +  ip + ", gateway: " + gateway + ", can not be empty");
300            return false;
301        }
302        int PrefixLen = countPrefixLength(NetworkUtils.numericToInetAddress(mask).getAddress());
303        mLinkProperties.addLinkAddress(new LinkAddress(NetworkUtils.numericToInetAddress(ip), PrefixLen));
304        RouteInfo ri = new RouteInfo(NetworkUtils.numericToInetAddress(gateway));
305        mLinkProperties.addRoute(ri);
306        if(!dns1.isEmpty())
307            mLinkProperties.addDns(NetworkUtils.numericToInetAddress(dns1));
308        if(!dns2.isEmpty())
309            mLinkProperties.addDns(NetworkUtils.numericToInetAddress(dns2));
310        mLinkProperties.setInterfaceName(iface);
311        return true;
312    }
313    public synchronized void startReverseTether(String iface) {
314        mIface = iface;
315        if (DBG) Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
316         mDhcpThread = new Thread(new Runnable() {
317            public void run() {
318                //TODO(): Add callbacks for failure and success case.
319                //Currently this thread runs independently.
320                if (DBG) Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
321                String DhcpResultName = "dhcp." + mIface + ".result";;
322                String result = "";
323                if (VDBG) Log.d(TAG, "waiting for change of sys prop dhcp result: " + DhcpResultName);
324                for(int i = 0; i < 30*5; i++) {
325                    try { Thread.sleep(200); } catch (InterruptedException ie) { return;}
326                    result = SystemProperties.get(DhcpResultName);
327                    if (VDBG) Log.d(TAG, "read " + DhcpResultName + ": " + result);
328                    if(result.equals("failed")) {
329                        Log.e(TAG, "startReverseTether, failed to start dhcp service");
330                        return;
331                    }
332                    if(result.equals("ok")) {
333                        if (VDBG) Log.d(TAG, "startReverseTether, dhcp resut: " + result);
334                        if(readLinkProperty(mIface)) {
335
336                            mNetworkInfo.setIsAvailable(true);
337                            mNetworkInfo.setDetailedState(DetailedState.CONNECTED, null, null);
338
339                            if (VDBG) Log.d(TAG, "startReverseTether mCsHandler: " + mCsHandler);
340                            if(mCsHandler != null) {
341                                Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
342                                msg.sendToTarget();
343
344                                msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
345                                msg.sendToTarget();
346                            }
347                        }
348                        return;
349                    }
350                }
351                Log.e(TAG, "startReverseTether, dhcp failed, resut: " + result);
352            }
353        });
354        mDhcpThread.start();
355    }
356
357    public synchronized void stopReverseTether() {
358        //NetworkUtils.stopDhcp(iface);
359        if(mDhcpThread != null && mDhcpThread.isAlive()) {
360            mDhcpThread.interrupt();
361            try { mDhcpThread.join(); } catch (InterruptedException ie) { return; }
362        }
363        mLinkProperties.clear();
364        mNetworkInfo.setIsAvailable(false);
365        mNetworkInfo.setDetailedState(DetailedState.DISCONNECTED, null, null);
366
367        Message msg = mCsHandler.obtainMessage(EVENT_CONFIGURATION_CHANGED, mNetworkInfo);
368        msg.sendToTarget();
369
370        msg = mCsHandler.obtainMessage(EVENT_STATE_CHANGED, mNetworkInfo);
371        msg.sendToTarget();
372    }
373
374    public void setDependencyMet(boolean met) {
375        // not supported on this network
376    }
377}
378