Merge "Introduce network link quality statistics" into klp-dev
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/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/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 07a7fba..ec6b063 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -31,6 +31,7 @@
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
 import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
 
+import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -56,6 +57,7 @@
 import android.net.INetworkStatsService;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.LinkInfo;
 import android.net.LinkProperties.CompareResult;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkConfig;
@@ -68,6 +70,7 @@
 import android.net.Proxy;
 import android.net.ProxyProperties;
 import android.net.RouteInfo;
+import android.net.SamplingDataTracker;
 import android.net.Uri;
 import android.net.wifi.WifiStateTracker;
 import android.net.wimax.WimaxManagerConstants;
@@ -144,8 +147,10 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.GregorianCalendar;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -174,6 +179,23 @@
     private static final String FAIL_FAST_TIME_MS =
             "persist.radio.fail_fast_time_ms";
 
+    private static final String ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED =
+            "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED";
+
+    private static final int SAMPLE_INTERVAL_ELAPSED_REQURST_CODE = 0;
+
+    private PendingIntent mSampleIntervalElapsedIntent;
+
+    // Set network sampling interval at 12 minutes, this way, even if the timers get
+    // aggregated, it will fire at around 15 minutes, which should allow us to
+    // aggregate this timer with other timers (specially the socket keep alive timers)
+    private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 12 * 60);
+
+    // start network sampling a minute after booting ...
+    private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 60);
+
+    AlarmManager mAlarmManager;
+
     // used in recursive route setting to add gateways for the host for which
     // a host route was requested.
     private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
@@ -326,6 +348,11 @@
      */
     private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
 
+    /**
+     * user internally to indicate that data sampling interval is up
+     */
+    private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15;
+
     /** Handler used for internal events. */
     private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -392,6 +419,7 @@
     List mProtectedNetworks;
 
     private DataConnectionStats mDataConnectionStats;
+
     private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
 
     TelephonyManager mTelephonyManager;
@@ -634,6 +662,29 @@
         mDataConnectionStats = new DataConnectionStats(mContext);
         mDataConnectionStats.startMonitoring();
 
+        // start network sampling ..
+        Intent intent = new Intent(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED, null);
+        mSampleIntervalElapsedIntent = PendingIntent.getBroadcast(mContext,
+                SAMPLE_INTERVAL_ELAPSED_REQURST_CODE, intent, 0);
+
+        mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+        setAlarm(DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS * 1000, mSampleIntervalElapsedIntent);
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED)) {
+                            mHandler.sendMessage(mHandler.obtainMessage
+                                    (EVENT_SAMPLE_INTERVAL_ELAPSED));
+                        }
+                    }
+                },
+                new IntentFilter(filter));
+
         mPacManager = new PacManager(mContext);
     }
 
@@ -1911,6 +1962,7 @@
 //            if (mActiveDefaultNetwork != -1) {
 //                currentPriority = mNetConfigs[mActiveDefaultNetwork].mPriority;
 //            }
+
             for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
                 if (checkType == prevNetType) continue;
                 if (mNetConfigs[checkType] == null) continue;
@@ -1925,6 +1977,7 @@
 // optimization should work and we need to investigate why it doesn't work.
 // This could be related to how DEACTIVATE_DATA_CALL is reporting its
 // complete before it is really complete.
+
 //                if (!mNetTrackers[checkType].isAvailable()) continue;
 
 //                if (currentPriority >= mNetConfigs[checkType].mPriority) continue;
@@ -2520,12 +2573,12 @@
     }
 
 
-   /**
+    /**
      * Reads the network specific TCP buffer sizes from SystemProperties
      * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
      * wide use
      */
-   private void updateNetworkSettings(NetworkStateTracker nt) {
+    private void updateNetworkSettings(NetworkStateTracker nt) {
         String key = nt.getTcpBufferSizesPropName();
         String bufferSizes = key == null ? null : SystemProperties.get(key);
 
@@ -2547,7 +2600,7 @@
         }
     }
 
-   /**
+    /**
      * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max]
      * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem
      *
@@ -2956,6 +3009,9 @@
                                 + " != tag:" + tag);
                     }
                 }
+                case EVENT_SAMPLE_INTERVAL_ELAPSED:
+                    handleNetworkSamplingTimeout();
+                    break;
             }
         }
     }
@@ -4357,4 +4413,92 @@
             }
         }
     };
+
+    @Override
+    public LinkInfo getLinkInfo(int networkType) {
+        enforceAccessPermission();
+        if (isNetworkTypeValid(networkType)) {
+            return mNetTrackers[networkType].getLinkInfo();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public LinkInfo getActiveLinkInfo() {
+        enforceAccessPermission();
+        if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+            return mNetTrackers[mActiveDefaultNetwork].getLinkInfo();
+        } else {
+            return null;
+        }
+    }
+
+    @Override
+    public LinkInfo[] getAllLinkInfo() {
+        enforceAccessPermission();
+        final ArrayList<LinkInfo> result = Lists.newArrayList();
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                LinkInfo li = tracker.getLinkInfo();
+                if (li != null) {
+                    result.add(li);
+                }
+            }
+        }
+
+        return result.toArray(new LinkInfo[result.size()]);
+    }
+
+    /* Infrastructure for network sampling */
+
+    private void handleNetworkSamplingTimeout() {
+
+        log("Sampling interval elapsed, updating statistics ..");
+
+        // initialize list of interfaces ..
+        Map<String, SamplingDataTracker.SamplingSnapshot> mapIfaceToSample =
+                new HashMap<String, SamplingDataTracker.SamplingSnapshot>();
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                String ifaceName = tracker.getNetworkInterfaceName();
+                if (ifaceName != null) {
+                    mapIfaceToSample.put(ifaceName, null);
+                }
+            }
+        }
+
+        // Read samples for all interfaces
+        SamplingDataTracker.getSamplingSnapshots(mapIfaceToSample);
+
+        // process samples for all networks
+        for (NetworkStateTracker tracker : mNetTrackers) {
+            if (tracker != null) {
+                String ifaceName = tracker.getNetworkInterfaceName();
+                SamplingDataTracker.SamplingSnapshot ss = mapIfaceToSample.get(ifaceName);
+                if (ss != null) {
+                    // end the previous sampling cycle
+                    tracker.stopSampling(ss);
+                    // start a new sampling cycle ..
+                    tracker.startSampling(ss);
+                }
+            }
+        }
+
+        log("Done.");
+
+        int samplingIntervalInSeconds = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+                DEFAULT_SAMPLING_INTERVAL_IN_SECONDS);
+
+        if (DBG) log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds");
+
+        setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent);
+    }
+
+    void setAlarm(int timeoutInMilliseconds, PendingIntent intent) {
+        long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
+    }
 }
+