Map network identity using ConnectivityService.

Instead of deriving network identity based on raw subsystem broadcasts,
listen for updates from ConnectivityService.  Added atomic view of all
active NetworkState, and build map from "iface" to NetworkIdentity set
for stats tracking.

To avoid exposing internal complexity, INetworkStatsService calls use
general templates.  Added TelephonyManager mapping to classify network
types using broad labels like "3G" or "4G", used to drive templates.

Cleaned up Objects and Preconditions.

Change-Id: I1d4c1403f0503bc3635a59bb378841ba42239a91
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index c72c4b0..21cce44 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -256,10 +256,61 @@
 
     private final IConnectivityManager mService;
 
-    static public boolean isNetworkTypeValid(int networkType) {
+    public static boolean isNetworkTypeValid(int networkType) {
         return networkType >= 0 && networkType <= MAX_NETWORK_TYPE;
     }
 
+    /** {@hide} */
+    public static String getNetworkTypeName(int type) {
+        switch (type) {
+            case TYPE_MOBILE:
+                return "MOBILE";
+            case TYPE_WIFI:
+                return "WIFI";
+            case TYPE_MOBILE_MMS:
+                return "MOBILE_MMS";
+            case TYPE_MOBILE_SUPL:
+                return "MOBILE_SUPL";
+            case TYPE_MOBILE_DUN:
+                return "MOBILE_DUN";
+            case TYPE_MOBILE_HIPRI:
+                return "MOBILE_HIPRI";
+            case TYPE_WIMAX:
+                return "WIMAX";
+            case TYPE_BLUETOOTH:
+                return "BLUETOOTH";
+            case TYPE_DUMMY:
+                return "DUMMY";
+            case TYPE_ETHERNET:
+                return "ETHERNET";
+            case TYPE_MOBILE_FOTA:
+                return "MOBILE_FOTA";
+            case TYPE_MOBILE_IMS:
+                return "MOBILE_IMS";
+            case TYPE_MOBILE_CBS:
+                return "MOBILE_CBS";
+            default:
+                return Integer.toString(type);
+        }
+    }
+
+    /** {@hide} */
+    public static boolean isNetworkTypeMobile(int networkType) {
+        switch (networkType) {
+            case TYPE_MOBILE:
+            case TYPE_MOBILE_MMS:
+            case TYPE_MOBILE_SUPL:
+            case TYPE_MOBILE_DUN:
+            case TYPE_MOBILE_HIPRI:
+            case TYPE_MOBILE_FOTA:
+            case TYPE_MOBILE_IMS:
+            case TYPE_MOBILE_CBS:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     public void setNetworkPreference(int preference) {
         try {
             mService.setNetworkPreference(preference);
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 647a60a..07f6cec 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -18,6 +18,7 @@
 
 import android.net.LinkProperties;
 import android.net.NetworkInfo;
+import android.net.NetworkState;
 import android.net.ProxyProperties;
 import android.os.IBinder;
 
@@ -40,6 +41,8 @@
     LinkProperties getActiveLinkProperties();
     LinkProperties getLinkProperties(int networkType);
 
+    NetworkState[] getAllNetworkState();
+
     boolean setRadios(boolean onOff);
 
     boolean setRadio(int networkType, boolean turnOn);
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
new file mode 100644
index 0000000..749039a
--- /dev/null
+++ b/core/java/android/net/NetworkState.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * Snapshot of network state.
+ *
+ * @hide
+ */
+public class NetworkState implements Parcelable {
+
+    public final NetworkInfo networkInfo;
+    public final LinkProperties linkProperties;
+    public final LinkCapabilities linkCapabilities;
+
+    public NetworkState(NetworkInfo networkInfo, LinkProperties linkProperties,
+            LinkCapabilities linkCapabilities) {
+        this.networkInfo = networkInfo;
+        this.linkProperties = linkProperties;
+        this.linkCapabilities = linkCapabilities;
+    }
+
+    public NetworkState(Parcel in) {
+        networkInfo = in.readParcelable(null);
+        linkProperties = in.readParcelable(null);
+        linkCapabilities = in.readParcelable(null);
+    }
+
+    /** {@inheritDoc} */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(networkInfo, flags);
+        out.writeParcelable(linkProperties, flags);
+        out.writeParcelable(linkCapabilities, flags);
+    }
+
+    public static final Creator<NetworkState> CREATOR = new Creator<NetworkState>() {
+        public NetworkState createFromParcel(Parcel in) {
+            return new NetworkState(in);
+        }
+
+        public NetworkState[] newArray(int size) {
+            return new NetworkState[size];
+        }
+    };
+
+}
diff --git a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
index eb63c0d..69bbcd7 100644
--- a/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
+++ b/core/tests/coretests/src/android/net/NetworkStatsHistoryTest.java
@@ -16,8 +16,6 @@
 
 package android.net;
 
-import static android.net.ConnectivityManager.TYPE_MOBILE;
-import static android.net.NetworkStatsHistory.UID_ALL;
 import static android.text.format.DateUtils.DAY_IN_MILLIS;
 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
@@ -51,7 +49,7 @@
 
     public void testRecordSingleBucket() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
-        stats = buildStats(BUCKET_SIZE);
+        stats = new NetworkStatsHistory(BUCKET_SIZE);
 
         // record data into narrow window to get single bucket
         stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 1024L, 2048L);
@@ -62,7 +60,7 @@
 
     public void testRecordEqualBuckets() throws Exception {
         final long bucketDuration = HOUR_IN_MILLIS;
-        stats = buildStats(bucketDuration);
+        stats = new NetworkStatsHistory(bucketDuration);
 
         // split equally across two buckets
         final long recordStart = TEST_START + (bucketDuration / 2);
@@ -75,7 +73,7 @@
 
     public void testRecordTouchingBuckets() throws Exception {
         final long BUCKET_SIZE = 15 * MINUTE_IN_MILLIS;
-        stats = buildStats(BUCKET_SIZE);
+        stats = new NetworkStatsHistory(BUCKET_SIZE);
 
         // split almost completely into middle bucket, but with a few minutes
         // overlap into neighboring buckets. total record is 20 minutes.
@@ -94,7 +92,7 @@
 
     public void testRecordGapBuckets() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
-        stats = buildStats(BUCKET_SIZE);
+        stats = new NetworkStatsHistory(BUCKET_SIZE);
 
         // record some data today and next week with large gap
         final long firstStart = TEST_START;
@@ -122,7 +120,7 @@
 
     public void testRecordOverlapBuckets() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
-        stats = buildStats(BUCKET_SIZE);
+        stats = new NetworkStatsHistory(BUCKET_SIZE);
 
         // record some data in one bucket, and another overlapping buckets
         stats.recordData(TEST_START, TEST_START + SECOND_IN_MILLIS, 256L, 256L);
@@ -137,7 +135,7 @@
 
     public void testRemove() throws Exception {
         final long BUCKET_SIZE = HOUR_IN_MILLIS;
-        stats = buildStats(BUCKET_SIZE);
+        stats = new NetworkStatsHistory(BUCKET_SIZE);
 
         // record some data across 24 buckets
         stats.recordData(TEST_START, TEST_START + DAY_IN_MILLIS, 24L, 24L);
@@ -171,7 +169,7 @@
             // fuzzing with random events, looking for crashes
             final Random r = new Random();
             for (int i = 0; i < 500; i++) {
-                stats = buildStats(r.nextLong());
+                stats = new NetworkStatsHistory(r.nextLong());
                 for (int j = 0; j < 10000; j++) {
                     if (r.nextBoolean()) {
                         // add range
@@ -191,10 +189,6 @@
         }
     }
 
-    private static NetworkStatsHistory buildStats(long bucketSize) {
-        return new NetworkStatsHistory(TYPE_MOBILE, null, UID_ALL, bucketSize);
-    }
-
     private static void assertConsistent(NetworkStatsHistory stats) {
         // verify timestamps are monotonic
         for (int i = 1; i < stats.bucketCount; i++) {
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index b559fb9..e3d4c45 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -16,6 +16,7 @@
 
 package com.android.server;
 
+import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.UPDATE_DEVICE_STATS;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
 import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
@@ -38,6 +39,7 @@
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkState;
 import android.net.NetworkStateTracker;
 import android.net.NetworkUtils;
 import android.net.Proxy;
@@ -65,6 +67,7 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.server.connectivity.Tethering;
+import com.google.android.collect.Lists;
 
 import java.io.FileDescriptor;
 import java.io.IOException;
@@ -563,25 +566,32 @@
      */
     private boolean isNetworkBlocked(NetworkInfo info, int uid) {
         synchronized (mUidRules) {
-            return isNetworkBlockedLocked(info, uid);
+            // TODO: expand definition of "paid" network to cover tethered or
+            // paid hotspot use cases.
+            final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI;
+            final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+
+            if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) {
+                return true;
+            }
+
+            // no restrictive rules; network is visible
+            return false;
         }
     }
 
     /**
-     * Check if UID is blocked from using the given {@link NetworkInfo}.
+     * Return a filtered version of the given {@link NetworkInfo}, potentially
+     * marked {@link DetailedState#BLOCKED} based on
+     * {@link #isNetworkBlocked(NetworkInfo, int)}.
      */
-    private boolean isNetworkBlockedLocked(NetworkInfo info, int uid) {
-        // TODO: expand definition of "paid" network to cover tethered or paid
-        // hotspot use cases.
-        final boolean networkIsPaid = info.getType() != ConnectivityManager.TYPE_WIFI;
-        final int uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
-
-        if (networkIsPaid && (uidRules & RULE_REJECT_PAID) != 0) {
-            return true;
+    private NetworkInfo filterNetworkInfo(NetworkInfo info, int uid) {
+        if (isNetworkBlocked(info, uid)) {
+            // network is blocked; clone and override state
+            info = new NetworkInfo(info);
+            info.setDetailedState(DetailedState.BLOCKED, null, null);
         }
-
-        // no restrictive rules; network is visible
-        return false;
+        return info;
     }
 
     /**
@@ -616,12 +626,7 @@
         if (isNetworkTypeValid(networkType)) {
             final NetworkStateTracker tracker = mNetTrackers[networkType];
             if (tracker != null) {
-                info = tracker.getNetworkInfo();
-                if (isNetworkBlocked(info, uid)) {
-                    // network is blocked; clone and override state
-                    info = new NetworkInfo(info);
-                    info.setDetailedState(DetailedState.BLOCKED, null, null);
-                }
+                info = filterNetworkInfo(tracker.getNetworkInfo(), uid);
             }
         }
         return info;
@@ -631,22 +636,15 @@
     public NetworkInfo[] getAllNetworkInfo() {
         enforceAccessPermission();
         final int uid = Binder.getCallingUid();
-        final NetworkInfo[] result = new NetworkInfo[mNetworksDefined];
-        int i = 0;
+        final ArrayList<NetworkInfo> result = Lists.newArrayList();
         synchronized (mUidRules) {
             for (NetworkStateTracker tracker : mNetTrackers) {
                 if (tracker != null) {
-                    NetworkInfo info = tracker.getNetworkInfo();
-                    if (isNetworkBlockedLocked(info, uid)) {
-                        // network is blocked; clone and override state
-                        info = new NetworkInfo(info);
-                        info.setDetailedState(DetailedState.BLOCKED, null, null);
-                    }
-                    result[i++] = info;
+                    result.add(filterNetworkInfo(tracker.getNetworkInfo(), uid));
                 }
             }
         }
-        return result;
+        return result.toArray(new NetworkInfo[result.size()]);
     }
 
     /**
@@ -674,6 +672,23 @@
         return null;
     }
 
+    @Override
+    public NetworkState[] getAllNetworkState() {
+        enforceAccessPermission();
+        final int uid = Binder.getCallingUid();
+        final ArrayList<NetworkState> result = Lists.newArrayList();
+        synchronized (mUidRules) {
+            for (NetworkStateTracker tracker : mNetTrackers) {
+                if (tracker != null) {
+                    final NetworkInfo info = filterNetworkInfo(tracker.getNetworkInfo(), uid);
+                    result.add(new NetworkState(
+                            info, tracker.getLinkProperties(), tracker.getLinkCapabilities()));
+                }
+            }
+        }
+        return result.toArray(new NetworkState[result.size()]);
+    }
+
     public boolean setRadios(boolean turnOn) {
         boolean result = true;
         enforceChangePermission();