Introduce network link quality statistics

This change starts tracking traffic quality data for WiFi and mobile
networks. The quality is tracked based on incidental traffic, and not
on specific measurements. Theoretical bandwidths are hard-coded, as
well as sampling interval; although sampling interval can be changed
by setting a system policy.

Bugs filed to remove shortcomings of this change -

10342372 Change LinkInfo name to something better
10342318 Move hardcoded values of MobileLinkInfo to resources
         so they can be updated without changing code

Bug: 10006249

Change-Id: I83d8c7594da20fe53abbd5e1f909b1f606b035bb
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index 0aedecb..a9b7176 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -16,6 +16,7 @@
 
 package android.bluetooth;
 
+import android.net.BaseNetworkStateTracker;
 import android.os.IBinder;
 import android.os.ServiceManager;
 import android.os.INetworkManagementService;
@@ -54,7 +55,7 @@
  *
  * @hide
  */
-public class BluetoothTetheringDataTracker implements NetworkStateTracker {
+public class BluetoothTetheringDataTracker extends BaseNetworkStateTracker {
     private static final String NETWORKTYPE = "BLUETOOTH_TETHER";
     private static final String TAG = "BluetoothTethering";
     private static final boolean DBG = true;
@@ -66,18 +67,12 @@
     private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
 
     private final Object mLinkPropertiesLock = new Object();
-    private LinkProperties mLinkProperties;
-
-    private LinkCapabilities mLinkCapabilities;
-
     private final Object mNetworkInfoLock = new Object();
-    private NetworkInfo mNetworkInfo;
 
     private BluetoothPan mBluetoothPan;
     private static String mRevTetheredIface;
     /* For sending events to connectivity service handler */
     private Handler mCsHandler;
-    protected Context mContext;
     private static BluetoothTetheringDataTracker sInstance;
     private BtdtHandler mBtdtHandler;
     private AtomicReference<AsyncChannel> mAsyncChannel = new AtomicReference<AsyncChannel>(null);
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index e87f84c..c39488e 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -57,6 +57,10 @@
         mLinkCapabilities = new LinkCapabilities();
     }
 
+    protected BaseNetworkStateTracker() {
+        // By default, let the sub classes construct everything
+    }
+
     @Deprecated
     protected Handler getTargetHandler() {
         return mTarget;
@@ -73,30 +77,37 @@
     }
 
     @Override
-    public final void startMonitoring(Context context, Handler target) {
+    public void startMonitoring(Context context, Handler target) {
         mContext = Preconditions.checkNotNull(context);
         mTarget = Preconditions.checkNotNull(target);
         startMonitoringInternal();
     }
 
-    protected abstract void startMonitoringInternal();
+    protected void startMonitoringInternal() {
+
+    }
 
     @Override
-    public final NetworkInfo getNetworkInfo() {
+    public NetworkInfo getNetworkInfo() {
         return new NetworkInfo(mNetworkInfo);
     }
 
     @Override
-    public final LinkProperties getLinkProperties() {
+    public LinkProperties getLinkProperties() {
         return new LinkProperties(mLinkProperties);
     }
 
     @Override
-    public final LinkCapabilities getLinkCapabilities() {
+    public LinkCapabilities getLinkCapabilities() {
         return new LinkCapabilities(mLinkCapabilities);
     }
 
     @Override
+    public LinkInfo getLinkInfo() {
+        return null;
+    }
+
+    @Override
     public void captivePortalCheckComplete() {
         // not implemented
     }
@@ -176,4 +187,23 @@
     public void supplyMessenger(Messenger messenger) {
         // not supported on this network
     }
+
+    @Override
+    public String getNetworkInterfaceName() {
+        if (mLinkProperties != null) {
+            return mLinkProperties.getInterfaceName();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public void startSampling(SamplingDataTracker.SamplingSnapshot s) {
+        // nothing to do
+    }
+
+    @Override
+    public void stopSampling(SamplingDataTracker.SamplingSnapshot s) {
+        // nothing to do
+    }
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 1b418fa..f6a3a4a 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -1442,4 +1442,43 @@
         }
         return null;
     }
+
+    /**
+     * get the information about a specific network link
+     * @hide
+     */
+    public LinkInfo getLinkInfo(int networkType) {
+        try {
+            LinkInfo li = mService.getLinkInfo(networkType);
+            return li;
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * get the information of currently active network link
+     * @hide
+     */
+    public LinkInfo getActiveLinkInfo() {
+        try {
+            LinkInfo li = mService.getActiveLinkInfo();
+            return li;
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * get the information of all network links
+     * @hide
+     */
+    public LinkInfo[] getAllLinkInfo() {
+        try {
+            LinkInfo[] li = mService.getAllLinkInfo();
+            return li;
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
 }
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index ee738fd..51a1191 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -29,18 +29,14 @@
  *
  * {@hide}
  */
-public class DummyDataStateTracker implements NetworkStateTracker {
+public class DummyDataStateTracker extends BaseNetworkStateTracker {
 
     private static final String TAG = "DummyDataStateTracker";
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
-    private NetworkInfo mNetworkInfo;
     private boolean mTeardownRequested = false;
     private Handler mTarget;
-    private Context mContext;
-    private LinkProperties mLinkProperties;
-    private LinkCapabilities mLinkCapabilities;
     private boolean mPrivateDnsRouteSet = false;
     private boolean mDefaultRouteSet = false;
 
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 7999c66..3a21a56 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -36,7 +36,7 @@
  * ConnectivityService.
  * @hide
  */
-public class EthernetDataTracker implements NetworkStateTracker {
+public class EthernetDataTracker extends BaseNetworkStateTracker {
     private static final String NETWORKTYPE = "ETHERNET";
     private static final String TAG = "Ethernet";
 
@@ -46,15 +46,11 @@
     private AtomicBoolean mDefaultRouteSet = new AtomicBoolean(false);
 
     private static boolean mLinkUp;
-    private LinkProperties mLinkProperties;
-    private LinkCapabilities mLinkCapabilities;
-    private NetworkInfo mNetworkInfo;
     private InterfaceObserver mInterfaceObserver;
     private String mHwAddr;
 
     /* For sending events to connectivity service handler */
     private Handler mCsHandler;
-    private Context mContext;
 
     private static EthernetDataTracker sInstance;
     private static String sIfaceMatch = "";
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 992ec37..bf2dade 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -16,6 +16,7 @@
 
 package android.net;
 
+import android.net.LinkInfo;
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
 import android.net.NetworkQuotaInfo;
@@ -145,4 +146,11 @@
     String getMobileProvisioningUrl();
 
     String getMobileRedirectedProvisioningUrl();
+
+    LinkInfo getLinkInfo(int networkType);
+
+    LinkInfo getActiveLinkInfo();
+
+    LinkInfo[] getAllLinkInfo();
+
 }
diff --git a/core/java/android/net/LinkInfo.aidl b/core/java/android/net/LinkInfo.aidl
new file mode 100644
index 0000000..716674b
--- /dev/null
+++ b/core/java/android/net/LinkInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2013 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 android.net;
+
+parcelable LinkInfo;
diff --git a/core/java/android/net/LinkInfo.java b/core/java/android/net/LinkInfo.java
new file mode 100644
index 0000000..98e8f35
--- /dev/null
+++ b/core/java/android/net/LinkInfo.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2013 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 android.net;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ *  Class that represents useful attributes of generic network links
+ *  such as the upload/download throughput or packet error rate.
+ *  Generally speaking, you should be dealing with instances of
+ *  LinkInfo subclasses, such as {@link android.net.#WifiLinkInfo}
+ *  or {@link android.net.#MobileLinkInfo} which provide additional
+ *  information.
+ *  @hide
+ */
+public class LinkInfo implements Parcelable
+{
+    public static final int UNKNOWN = Integer.MAX_VALUE;
+
+    public static final int NORMALIZED_MIN_SIGNAL_STRENGTH = 0;
+
+    public static final int NORMALIZED_MAX_SIGNAL_STRENGTH = 99;
+
+    public static final int NORMALIZED_SIGNAL_STRENGTH_RANGE = NORMALIZED_MAX_SIGNAL_STRENGTH + 1;
+
+    /* Network type as defined by ConnectivityManager */
+    public int mNetworkType = ConnectivityManager.TYPE_NONE;
+
+    public int mNormalizedSignalStrength = UNKNOWN;
+
+    public int mPacketCount = UNKNOWN;
+    public int mPacketErrorCount = UNKNOWN;
+    public int mTheoreticalTxBandwidth = UNKNOWN;
+    public int mTheoreticalRxBandwidth = UNKNOWN;
+    public int mTheoreticalLatency = UNKNOWN;
+
+    /* Timestamp when last sample was made available */
+    public long mLastDataSampleTime = UNKNOWN;
+
+    /* Sample duration in millisecond */
+    public int mDataSampleDuration = UNKNOWN;
+
+    public LinkInfo() {
+
+    }
+
+    /**
+     * Implement the Parcelable interface
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+    /**
+     * Implement the Parcelable interface.
+     */
+
+    protected static final int OBJECT_TYPE_LINKINFO = 1;
+    protected static final int OBJECT_TYPE_WIFI_LINKINFO = 2;
+    protected static final int OBJECT_TYPE_MOBILE_LINKINFO = 3;
+
+    public void writeToParcel(Parcel dest, int flags) {
+        writeToParcel(dest, flags, OBJECT_TYPE_LINKINFO);
+    }
+
+    public void writeToParcel(Parcel dest, int flags, int objectType) {
+        dest.writeInt(objectType);
+        dest.writeInt(mNetworkType);
+        dest.writeInt(mNormalizedSignalStrength);
+        dest.writeInt(mPacketCount);
+        dest.writeInt(mPacketErrorCount);
+        dest.writeInt(mTheoreticalTxBandwidth);
+        dest.writeInt(mTheoreticalRxBandwidth);
+        dest.writeInt(mTheoreticalLatency);
+        dest.writeLong(mLastDataSampleTime);
+        dest.writeInt(mDataSampleDuration);
+    }
+
+    public static final Creator<LinkInfo> CREATOR =
+            new Creator<LinkInfo>() {
+                public LinkInfo createFromParcel(Parcel in) {
+                    int objectType = in.readInt();
+                    if (objectType == OBJECT_TYPE_LINKINFO) {
+                        LinkInfo li = new LinkInfo();
+                        li.initializeFromParcel(in);
+                        return li;
+                    } else if (objectType == OBJECT_TYPE_WIFI_LINKINFO) {
+                        return WifiLinkInfo.createFromParcelBody(in);
+                    } else if (objectType == OBJECT_TYPE_MOBILE_LINKINFO) {
+                        return MobileLinkInfo.createFromParcelBody(in);
+                    } else {
+                        return null;
+                    }
+                }
+
+                public LinkInfo[] newArray(int size) {
+                    return new LinkInfo[size];
+                }
+            };
+
+    protected void initializeFromParcel(Parcel in) {
+        mNetworkType = in.readInt();
+        mNormalizedSignalStrength = in.readInt();
+        mPacketCount = in.readInt();
+        mPacketErrorCount = in.readInt();
+        mTheoreticalTxBandwidth = in.readInt();
+        mTheoreticalRxBandwidth = in.readInt();
+        mTheoreticalLatency = in.readInt();
+        mLastDataSampleTime = in.readLong();
+        mDataSampleDuration = in.readInt();
+    }
+
+}
diff --git a/core/java/android/net/LinkProperties.aidl b/core/java/android/net/LinkProperties.aidl
index 9cd06d5..3cb9525 100644
--- a/core/java/android/net/LinkProperties.aidl
+++ b/core/java/android/net/LinkProperties.aidl
@@ -18,4 +18,3 @@
 package android.net;
 
 parcelable LinkProperties;
-
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index e4fd312..faa13b0 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -28,6 +28,8 @@
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.PhoneStateListener;
+import android.telephony.SignalStrength;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Slog;
@@ -49,7 +51,7 @@
  *
  * {@hide}
  */
-public class MobileDataStateTracker implements NetworkStateTracker {
+public class MobileDataStateTracker extends BaseNetworkStateTracker {
 
     private static final String TAG = "MobileDataStateTracker";
     private static final boolean DBG = true;
@@ -59,12 +61,8 @@
     private ITelephony mPhoneService;
 
     private String mApnType;
-    private NetworkInfo mNetworkInfo;
     private boolean mTeardownRequested = false;
     private Handler mTarget;
-    private Context mContext;
-    private LinkProperties mLinkProperties;
-    private LinkCapabilities mLinkCapabilities;
     private boolean mPrivateDnsRouteSet = false;
     private boolean mDefaultRouteSet = false;
 
@@ -78,6 +76,10 @@
 
     private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false);
 
+    private SignalStrength mSignalStrength;
+
+    private SamplingDataTracker mSamplingDataTracker = new SamplingDataTracker();
+
     /**
      * Create a new MobileDataStateTracker
      * @param netType the ConnectivityManager network type
@@ -108,8 +110,19 @@
 
         mContext.registerReceiver(new MobileDataStateReceiver(), filter);
         mMobileDataState = PhoneConstants.DataState.DISCONNECTED;
+
+        TelephonyManager tm = (TelephonyManager)mContext.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        tm.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
     }
 
+    private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+            mSignalStrength = signalStrength;
+        }
+    };
+
     static class MdstHandler extends Handler {
         private MobileDataStateTracker mMdst;
 
@@ -251,6 +264,30 @@
                             setDetailedState(DetailedState.CONNECTED, reason, apnName);
                             break;
                     }
+
+                    if (VDBG) {
+                        Slog.d(TAG, "TelephonyMgr.DataConnectionStateChanged");
+                        if (mNetworkInfo != null) {
+                            Slog.d(TAG, "NetworkInfo = " + mNetworkInfo.toString());
+                            Slog.d(TAG, "subType = " + String.valueOf(mNetworkInfo.getSubtype()));
+                            Slog.d(TAG, "subType = " + mNetworkInfo.getSubtypeName());
+                        }
+                        if (mLinkProperties != null) {
+                            Slog.d(TAG, "LinkProperties = " + mLinkProperties.toString());
+                        } else {
+                            Slog.d(TAG, "LinkProperties = " );
+                        }
+
+                        if (mLinkCapabilities != null) {
+                            Slog.d(TAG, "LinkCapabilities = " + mLinkCapabilities.toString());
+                        } else {
+                            Slog.d(TAG, "LinkCapabilities = " );
+                        }
+                    }
+
+
+                    /* lets not sample traffic data across state changes */
+                    mSamplingDataTracker.resetSamplingData();
                 } else {
                     // There was no state change. Check if LinkProperties has been updated.
                     if (TextUtils.equals(reason, PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
@@ -283,7 +320,7 @@
                 String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
                 if (DBG) {
                     log("Received " + intent.getAction() +
-                                " broadcast" + reason == null ? "" : "(" + reason + ")");
+                                " broadcast" + (reason == null ? "" : "(" + reason + ")"));
                 }
                 setDetailedState(DetailedState.FAILED, reason, apnName);
             } else {
@@ -557,7 +594,7 @@
         return writer.toString();
     }
 
-   /**
+    /**
      * Internal method supporting the ENABLE_MMS feature.
      * @param apnType the type of APN to be enabled or disabled (e.g., mms)
      * @param enable {@code true} to enable the specified APN type,
@@ -617,9 +654,11 @@
         }
     }
 
+
     /**
      * @see android.net.NetworkStateTracker#getLinkProperties()
      */
+    @Override
     public LinkProperties getLinkProperties() {
         return new LinkProperties(mLinkProperties);
     }
@@ -627,6 +666,7 @@
     /**
      * @see android.net.NetworkStateTracker#getLinkCapabilities()
      */
+    @Override
     public LinkCapabilities getLinkCapabilities() {
         return new LinkCapabilities(mLinkCapabilities);
     }
@@ -648,4 +688,152 @@
     static private void sloge(String s) {
         Slog.e(TAG, s);
     }
+
+    @Override
+    public LinkInfo getLinkInfo() {
+        if (mNetworkInfo == null || mNetworkInfo.getType() == ConnectivityManager.TYPE_NONE) {
+            // no data available yet; just return
+            return null;
+        }
+
+        MobileLinkInfo li = new MobileLinkInfo();
+
+        li.mNetworkType = mNetworkInfo.getType();
+
+        mSamplingDataTracker.setCommonLinkInfoFields(li);
+
+        if (mNetworkInfo.getSubtype() != TelephonyManager.NETWORK_TYPE_UNKNOWN) {
+            li.mMobileNetworkType = mNetworkInfo.getSubtype();
+
+            NetworkDataEntry entry = getNetworkDataEntry(mNetworkInfo.getSubtype());
+            if (entry != null) {
+                li.mTheoreticalRxBandwidth = entry.downloadBandwidth;
+                li.mTheoreticalRxBandwidth = entry.uploadBandwidth;
+                li.mTheoreticalLatency = entry.latency;
+            }
+
+            if (mSignalStrength != null) {
+                li.mNormalizedSignalStrength = getNormalizedSignalStrength(
+                        li.mMobileNetworkType, mSignalStrength);
+            }
+        }
+
+        SignalStrength ss = mSignalStrength;
+        if (ss != null) {
+
+            li.mRssi = ss.getGsmSignalStrength();
+            li.mGsmErrorRate = ss.getGsmBitErrorRate();
+            li.mCdmaDbm = ss.getCdmaDbm();
+            li.mCdmaEcio = ss.getCdmaEcio();
+            li.mEvdoDbm = ss.getEvdoDbm();
+            li.mEvdoEcio = ss.getEvdoEcio();
+            li.mEvdoSnr = ss.getEvdoSnr();
+            li.mLteSignalStrength = ss.getLteSignalStrength();
+            li.mLteRsrp = ss.getLteRsrp();
+            li.mLteRsrq = ss.getLteRsrq();
+            li.mLteRssnr = ss.getLteRssnr();
+            li.mLteCqi = ss.getLteCqi();
+        }
+
+        if (VDBG) {
+            Slog.d(TAG, "Returning LinkInfo with"
+                    + " MobileNetworkType = " + String.valueOf(li.mMobileNetworkType)
+                    + " Theoretical Rx BW = " + String.valueOf(li.mTheoreticalRxBandwidth)
+                    + " gsm Signal Strength = " + String.valueOf(li.mRssi)
+                    + " cdma Signal Strength = " + String.valueOf(li.mCdmaDbm)
+                    + " evdo Signal Strength = " + String.valueOf(li.mEvdoDbm)
+                    + " Lte Signal Strength = " + String.valueOf(li.mLteSignalStrength));
+        }
+
+        return li;
+    }
+
+    static class NetworkDataEntry {
+        public int networkType;
+        public int downloadBandwidth;               // in kbps
+        public int uploadBandwidth;                 // in kbps
+        public int latency;                         // in millisecond
+
+        NetworkDataEntry(int i1, int i2, int i3, int i4) {
+            networkType = i1;
+            downloadBandwidth = i2;
+            uploadBandwidth = i3;
+            latency = i4;
+        }
+    }
+
+    private static NetworkDataEntry [] mTheoreticalBWTable = new NetworkDataEntry[] {
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EDGE,     237,   118, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_GPRS,      48,    40, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_UMTS,     384,    64, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSDPA,  14400,    -1, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSUPA,  14400,  5760, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPA,   14400,  5760, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_HSPAP,  21000,  5760, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_CDMA,      -1,    -1, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_1xRTT,     -1,    -1, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_0,  2468,   153, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_A,  3072,  1800, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EVDO_B, 14700,  1800, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_IDEN,      -1,    -1, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_LTE,   100000, 50000, -1),
+            new NetworkDataEntry(TelephonyManager.NETWORK_TYPE_EHRPD,     -1,    -1, -1),
+    };
+
+    private static NetworkDataEntry getNetworkDataEntry(int networkType) {
+        for (NetworkDataEntry entry : mTheoreticalBWTable) {
+            if (entry.networkType == networkType) {
+                return entry;
+            }
+        }
+
+        Slog.e(TAG, "Could not find Theoretical BW entry for " + String.valueOf(networkType));
+        return null;
+    }
+
+    private static int getNormalizedSignalStrength(int networkType, SignalStrength ss) {
+
+        int level;
+
+        switch(networkType) {
+            case TelephonyManager.NETWORK_TYPE_EDGE:
+            case TelephonyManager.NETWORK_TYPE_GPRS:
+            case TelephonyManager.NETWORK_TYPE_UMTS:
+            case TelephonyManager.NETWORK_TYPE_HSDPA:
+            case TelephonyManager.NETWORK_TYPE_HSUPA:
+            case TelephonyManager.NETWORK_TYPE_HSPA:
+            case TelephonyManager.NETWORK_TYPE_HSPAP:
+                level = ss.getGsmLevel();
+                break;
+            case TelephonyManager.NETWORK_TYPE_CDMA:
+            case TelephonyManager.NETWORK_TYPE_1xRTT:
+                level = ss.getCdmaLevel();
+                break;
+            case TelephonyManager.NETWORK_TYPE_EVDO_0:
+            case TelephonyManager.NETWORK_TYPE_EVDO_A:
+            case TelephonyManager.NETWORK_TYPE_EVDO_B:
+                level = ss.getEvdoLevel();
+                break;
+            case TelephonyManager.NETWORK_TYPE_LTE:
+                level = ss.getLteLevel();
+                break;
+            case TelephonyManager.NETWORK_TYPE_IDEN:
+            case TelephonyManager.NETWORK_TYPE_EHRPD:
+            default:
+                return LinkInfo.UNKNOWN;
+        }
+
+        return (level * LinkInfo.NORMALIZED_SIGNAL_STRENGTH_RANGE) /
+                SignalStrength.NUM_SIGNAL_STRENGTH_BINS;
+    }
+
+    @Override
+    public void startSampling(SamplingDataTracker.SamplingSnapshot s) {
+        mSamplingDataTracker.startSampling(s);
+    }
+
+    @Override
+    public void stopSampling(SamplingDataTracker.SamplingSnapshot s) {
+        mSamplingDataTracker.stopSampling(s);
+    }
 }
diff --git a/core/java/android/net/MobileLinkInfo.java b/core/java/android/net/MobileLinkInfo.java
new file mode 100644
index 0000000..2d18275
--- /dev/null
+++ b/core/java/android/net/MobileLinkInfo.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2013 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 android.net;
+
+import android.os.Parcel;
+import android.net.LinkInfo;
+
+/**
+ *  Class that represents useful attributes of mobile network links
+ *  such as the upload/download throughput or error rate etc.
+ *  @hide
+ */
+public final class MobileLinkInfo extends LinkInfo
+{
+    // Represents TelephonyManager.NetworkType
+    public int mMobileNetworkType = UNKNOWN;
+    public int mRssi = UNKNOWN;
+    public int mGsmErrorRate = UNKNOWN;
+    public int mCdmaDbm = UNKNOWN;
+    public int mCdmaEcio = UNKNOWN;
+    public int mEvdoDbm = UNKNOWN;
+    public int mEvdoEcio = UNKNOWN;
+    public int mEvdoSnr = UNKNOWN;
+    public int mLteSignalStrength = UNKNOWN;
+    public int mLteRsrp = UNKNOWN;
+    public int mLteRsrq = UNKNOWN;
+    public int mLteRssnr = UNKNOWN;
+    public int mLteCqi = UNKNOWN;
+
+    /**
+     * Implement the Parcelable interface.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags, OBJECT_TYPE_MOBILE_LINKINFO);
+
+        dest.writeInt(mMobileNetworkType);
+        dest.writeInt(mRssi);
+        dest.writeInt(mGsmErrorRate);
+        dest.writeInt(mCdmaDbm);
+        dest.writeInt(mCdmaEcio);
+        dest.writeInt(mEvdoDbm);
+        dest.writeInt(mEvdoEcio);
+        dest.writeInt(mEvdoSnr);
+        dest.writeInt(mLteSignalStrength);
+        dest.writeInt(mLteRsrp);
+        dest.writeInt(mLteRsrq);
+        dest.writeInt(mLteRssnr);
+        dest.writeInt(mLteCqi);
+    }
+
+    /* Un-parceling helper */
+    public static MobileLinkInfo createFromParcelBody(Parcel in) {
+
+        MobileLinkInfo li = new MobileLinkInfo();
+
+        li.initializeFromParcel(in);
+
+        li.mMobileNetworkType = in.readInt();
+        li.mRssi = in.readInt();
+        li.mGsmErrorRate = in.readInt();
+        li.mCdmaDbm = in.readInt();
+        li.mCdmaEcio = in.readInt();
+        li.mEvdoDbm = in.readInt();
+        li.mEvdoEcio = in.readInt();
+        li.mEvdoSnr = in.readInt();
+        li.mLteSignalStrength = in.readInt();
+        li.mLteRsrp = in.readInt();
+        li.mLteRsrq = in.readInt();
+        li.mLteRssnr = in.readInt();
+        li.mLteCqi = in.readInt();
+
+        return li;
+    }
+}
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index 9ed7533..a3d7b14 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -83,7 +83,6 @@
      */
     public static final int EVENT_NETWORK_DISCONNECTED = BASE_NETWORK_STATE_TRACKER + 5;
 
-
     /**
      * -------------------------------------------------------------
      * Control Interface
@@ -120,6 +119,12 @@
     public LinkCapabilities getLinkCapabilities();
 
     /**
+     * Get interesting information about this network link
+     * @return a copy of link information, null if not available
+     */
+    public LinkInfo getLinkInfo();
+
+    /**
      * Return the system properties name associated with the tcp buffer sizes
      * for this network.
      */
@@ -234,4 +239,20 @@
      * the underlying network specific code.
      */
     public void supplyMessenger(Messenger messenger);
+
+    /*
+     * Network interface name that we'll lookup for sampling data
+     */
+    public String getNetworkInterfaceName();
+
+    /*
+     * Save the starting sample
+     */
+    public void startSampling(SamplingDataTracker.SamplingSnapshot s);
+
+    /*
+     * Save the ending sample
+     */
+    public void stopSampling(SamplingDataTracker.SamplingSnapshot s);
+
 }
diff --git a/core/java/android/net/SamplingDataTracker.java b/core/java/android/net/SamplingDataTracker.java
new file mode 100644
index 0000000..b5dc140
--- /dev/null
+++ b/core/java/android/net/SamplingDataTracker.java
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2013 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 android.net;
+
+
+import android.os.SystemClock;
+import android.util.Slog;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * @hide
+ */
+public class SamplingDataTracker
+{
+    private static final boolean DBG = false;
+    private static final String  TAG = "SamplingDataTracker";
+
+    public static class SamplingSnapshot
+    {
+        public int  mTxByteCount;
+        public int  mRxByteCount;
+        public int  mTxPacketCount;
+        public int  mRxPacketCount;
+        public int  mTxPacketErrorCount;
+        public int  mRxPacketErrorCount;
+        public long mTimestamp;
+    }
+
+    public static void getSamplingSnapshots(Map<String, SamplingSnapshot> mapIfaceToSample) {
+
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader("/proc/net/dev"));
+
+            // Skip over the line bearing column titles (there are 2 lines)
+            String line;
+            reader.readLine();
+            reader.readLine();
+
+            while ((line = reader.readLine()) != null) {
+
+                // remove leading whitespace
+                line = line.trim();
+
+                String[] tokens = line.split("[ ]+");
+                if (tokens.length < 17) {
+                    continue;
+                }
+
+                /* column format is
+                 * Interface  (Recv)bytes packets errs drop fifo frame compressed multicast \
+                 *            (Transmit)bytes packets errs drop fifo colls carrier compress
+                */
+
+                String currentIface = tokens[0].split(":")[0];
+                if (DBG) Slog.d(TAG, "Found data for interface " + currentIface);
+                if (mapIfaceToSample.containsKey(currentIface)) {
+
+                    SamplingSnapshot ss = new SamplingSnapshot();
+
+                    ss.mTxByteCount        = Integer.parseInt(tokens[1]);
+                    ss.mTxPacketCount      = Integer.parseInt(tokens[2]);
+                    ss.mTxPacketErrorCount = Integer.parseInt(tokens[3]);
+                    ss.mRxByteCount        = Integer.parseInt(tokens[9]);
+                    ss.mRxPacketCount      = Integer.parseInt(tokens[10]);
+                    ss.mRxPacketErrorCount = Integer.parseInt(tokens[11]);
+
+                    ss.mTimestamp          = SystemClock.elapsedRealtime();
+
+                    if (DBG) {
+                        Slog.d(TAG, "Interface = " + currentIface);
+                        Slog.d(TAG, "ByteCount = " + String.valueOf(ss.mTxByteCount));
+                        Slog.d(TAG, "TxPacketCount = " + String.valueOf(ss.mTxPacketCount));
+                        Slog.d(TAG, "TxPacketErrorCount = "
+                                + String.valueOf(ss.mTxPacketErrorCount));
+                        Slog.d(TAG, "RxByteCount = " + String.valueOf(ss.mRxByteCount));
+                        Slog.d(TAG, "RxPacketCount = " + String.valueOf(ss.mRxPacketCount));
+                        Slog.d(TAG, "RxPacketErrorCount = "
+                                + String.valueOf(ss.mRxPacketErrorCount));
+                        Slog.d(TAG, "Timestamp = " + String.valueOf(ss.mTimestamp));
+                        Slog.d(TAG, "---------------------------");
+                    }
+
+                    mapIfaceToSample.put(currentIface, ss);
+                }
+            }
+
+            if (DBG) {
+                Iterator it = mapIfaceToSample.entrySet().iterator();
+                while (it.hasNext()) {
+                    Map.Entry kvpair = (Map.Entry)it.next();
+                    if (kvpair.getValue() == null) {
+                        Slog.d(TAG, "could not find snapshot for interface " + kvpair.getKey());
+                    }
+                }
+            }
+        } catch(FileNotFoundException e) {
+            Slog.e(TAG, "could not find /proc/net/dev");
+        } catch (IOException e) {
+            Slog.e(TAG, "could not read /proc/net/dev");
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (IOException e) {
+                Slog.e(TAG, "could not close /proc/net/dev");
+            }
+        }
+    }
+
+    // Snapshots from previous sampling interval
+    private SamplingSnapshot mBeginningSample;
+    private SamplingSnapshot mEndingSample;
+
+    // Starting snapshot of current interval
+    private SamplingSnapshot mLastSample;
+
+    // Protects sampling data from concurrent access
+    public final Object mSamplingDataLock = new Object();
+
+    // We need long enough time for a good sample
+    private final int MINIMUM_SAMPLING_INTERVAL = 15 * 1000;
+
+    // statistics is useless unless we have enough data
+    private final int MINIMUM_SAMPLED_PACKETS   = 30;
+
+    public void startSampling(SamplingSnapshot s) {
+        synchronized(mSamplingDataLock) {
+            mLastSample = s;
+        }
+    }
+
+    public void stopSampling(SamplingSnapshot s) {
+        synchronized(mSamplingDataLock) {
+            if (mLastSample != null) {
+                if (s.mTimestamp - mLastSample.mTimestamp > MINIMUM_SAMPLING_INTERVAL
+                        && getSampledPacketCount(mLastSample, s) > MINIMUM_SAMPLED_PACKETS) {
+                    mBeginningSample = mLastSample;
+                    mEndingSample = s;
+                    mLastSample = null;
+                } else {
+                    if (DBG) Slog.d(TAG, "Throwing current sample away because it is too small");
+                }
+            }
+        }
+    }
+
+    public void resetSamplingData() {
+        if (DBG) Slog.d(TAG, "Resetting sampled network data");
+        synchronized(mSamplingDataLock) {
+
+            // We could just take another sample here and treat it as an
+            // 'ending sample' effectively shortening sampling interval, but that
+            // requires extra work (specifically, reading the sample needs to be
+            // done asynchronously)
+
+            mLastSample = null;
+        }
+    }
+
+    public int getSampledTxByteCount() {
+        synchronized(mSamplingDataLock) {
+            if (mBeginningSample != null && mEndingSample != null) {
+                return mEndingSample.mTxByteCount - mBeginningSample.mTxByteCount;
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public int getSampledTxPacketCount() {
+        synchronized(mSamplingDataLock) {
+            if (mBeginningSample != null && mEndingSample != null) {
+                return mEndingSample.mTxPacketCount - mBeginningSample.mTxPacketCount;
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public int getSampledTxPacketErrorCount() {
+        synchronized(mSamplingDataLock) {
+            if (mBeginningSample != null && mEndingSample != null) {
+                return mEndingSample.mTxPacketErrorCount - mBeginningSample.mTxPacketErrorCount;
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public int getSampledRxByteCount() {
+        synchronized(mSamplingDataLock) {
+            if (mBeginningSample != null && mEndingSample != null) {
+                return mEndingSample.mRxByteCount - mBeginningSample.mRxByteCount;
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public int getSampledRxPacketCount() {
+        synchronized(mSamplingDataLock) {
+            if (mBeginningSample != null && mEndingSample != null) {
+                return mEndingSample.mRxPacketCount - mBeginningSample.mRxPacketCount;
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public int getSampledPacketCount() {
+        return getSampledPacketCount(mBeginningSample, mEndingSample);
+    }
+
+    public int getSampledPacketCount(SamplingSnapshot begin, SamplingSnapshot end) {
+        if (begin != null && end != null) {
+            int rxPacketCount = end.mRxPacketCount - begin.mRxPacketCount;
+            int txPacketCount = end.mTxPacketCount - begin.mTxPacketCount;
+            return rxPacketCount + txPacketCount;
+        } else {
+            return LinkInfo.UNKNOWN;
+        }
+    }
+
+    public int getSampledPacketErrorCount() {
+        if (mBeginningSample != null && mEndingSample != null) {
+            int rxPacketErrorCount = getSampledRxPacketErrorCount();
+            int txPacketErrorCount = getSampledTxPacketErrorCount();
+            return rxPacketErrorCount + txPacketErrorCount;
+        } else {
+            return LinkInfo.UNKNOWN;
+        }
+    }
+
+    public int getSampledRxPacketErrorCount() {
+        synchronized(mSamplingDataLock) {
+            if (mBeginningSample != null && mEndingSample != null) {
+                return mEndingSample.mRxPacketErrorCount - mBeginningSample.mRxPacketErrorCount;
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public long getSampleTimestamp() {
+        synchronized(mSamplingDataLock) {
+            if (mEndingSample != null) {
+                return mEndingSample.mTimestamp;
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public int getSampleDuration() {
+        synchronized(mSamplingDataLock) {
+            if (mBeginningSample != null && mEndingSample != null) {
+                return (int) (mEndingSample.mTimestamp - mBeginningSample.mTimestamp);
+            } else {
+                return LinkInfo.UNKNOWN;
+            }
+        }
+    }
+
+    public void setCommonLinkInfoFields(LinkInfo li) {
+        synchronized(mSamplingDataLock) {
+            li.mLastDataSampleTime = getSampleTimestamp();
+            li.mDataSampleDuration = getSampleDuration();
+            li.mPacketCount = getSampledPacketCount();
+            li.mPacketErrorCount = getSampledPacketErrorCount();
+        }
+    }
+}
+
diff --git a/core/java/android/net/WifiLinkInfo.java b/core/java/android/net/WifiLinkInfo.java
new file mode 100644
index 0000000..f3b0032
--- /dev/null
+++ b/core/java/android/net/WifiLinkInfo.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2013 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 android.net;
+
+import android.os.Parcel;
+import android.net.LinkInfo;
+
+/**
+ *  Class that represents useful attributes of wifi network links
+ *  such as the upload/download throughput or error rate etc.
+ *  @hide
+ */
+public final class WifiLinkInfo extends LinkInfo
+{
+    /**
+     * Type enumerations for Wifi Network
+     */
+
+    /* Indicates Wifi network type such as b/g etc*/
+    public int  mType = UNKNOWN;
+
+    public String mBssid;
+
+    /* Rssi found by scans */
+    public int  mRssi = UNKNOWN;
+
+    /* packet statistics */
+    public int  mTxGood = UNKNOWN;
+    public int  mTxBad = UNKNOWN;
+
+    /**
+     * Implement the Parcelable interface.
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        super.writeToParcel(dest, flags, OBJECT_TYPE_WIFI_LINKINFO);
+
+        dest.writeInt(mType);
+        dest.writeInt(mRssi);
+        dest.writeInt(mTxGood);
+        dest.writeInt(mTxBad);
+
+        dest.writeString(mBssid);
+    }
+
+    /* Un-parceling helper */
+    public static WifiLinkInfo createFromParcelBody(Parcel in) {
+        WifiLinkInfo li = new WifiLinkInfo();
+
+        li.initializeFromParcel(in);
+
+        li.mType =  in.readInt();
+        li.mRssi =  in.readInt();
+        li.mTxGood =  in.readInt();
+        li.mTxBad =  in.readInt();
+
+        li.mBssid =  in.readString();
+
+        return li;
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 585115a..c5fd391 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -5346,6 +5346,17 @@
          */
         public static final String CONNECTIVITY_CHANGE_DELAY = "connectivity_change_delay";
 
+
+        /**
+         * Network sampling interval, in seconds. We'll generate link information
+         * about bytes/packets sent and error rates based on data sampled in this interval
+         *
+         * @hide
+         */
+
+        public static final String CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS =
+                "connectivity_sampling_interval_in_seconds";
+
         /**
          * The series of successively longer delays used in retrying to download PAC file.
          * Last delay is used between successful PAC downloads.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1363e3c..a0f53b6 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -202,6 +202,8 @@
     <protected-broadcast android:name="android.net.conn.TETHER_STATE_CHANGED" />
     <protected-broadcast android:name="android.net.conn.INET_CONDITION_ACTION" />
     <protected-broadcast android:name="android.net.conn.NETWORK_CONDITIONS_MEASURED" />
+    <protected-brodcast
+            android:name="android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED" />
     <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE" />
     <protected-broadcast android:name="android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE" />
     <protected-broadcast android:name="android.intent.action.AIRPLANE_MODE" />