Network data activity change intent for network interfaces.

The activity notification is received from netd, an intent
DATA_ACTIVITY_CHANGE is then raised for other part of the system to
consume.

Change-Id: Idfcc4763c51c5b314c57f546c12557082f06bebf
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 5f8793c..fa1ff85 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -137,6 +137,28 @@
     public static final String EXTRA_INET_CONDITION = "inetCondition";
 
     /**
+     * Broadcast action to indicate the change of data activity status
+     * (idle or active) on a network in a recent period.
+     * The network becomes active when data transimission is started, or
+     * idle if there is no data transimition for a period of time.
+     * {@hide}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DATA_ACTIVITY_CHANGE = "android.net.conn.DATA_ACTIVITY_CHANGE";
+    /**
+     * The lookup key for an enum that indicates the network device type on which this data activity
+     * change happens.
+     * {@hide}
+     */
+    public static final String EXTRA_DEVICE_TYPE = "deviceType";
+    /**
+     * The lookup key for a boolean that indicates the device is active or not. {@code true} means
+     * it is actively sending or receiving data and {@code false} means it is idle.
+     * {@hide}
+     */
+    public static final String EXTRA_IS_ACTIVE = "isActive";
+
+    /**
      * Broadcast Action: The setting for background data usage has changed
      * values. Use {@link #getBackgroundDataSetting()} to get the current value.
      * <p>
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 5197c9e..c690430 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -99,6 +99,10 @@
         public void limitReached(String limitName, String iface) {
             // Ignored.
         }
+
+        public void interfaceClassDataActivityChanged(String label, boolean active) {
+            // Ignored.
+        }
     }
 
     private EthernetDataTracker() {
diff --git a/core/java/android/net/INetworkManagementEventObserver.aidl b/core/java/android/net/INetworkManagementEventObserver.aidl
index a97f203..6f4dd5f 100644
--- a/core/java/android/net/INetworkManagementEventObserver.aidl
+++ b/core/java/android/net/INetworkManagementEventObserver.aidl
@@ -62,4 +62,11 @@
      */
     void limitReached(String limitName, String iface);
 
+    /**
+     * Interface data activity status is changed.
+     *
+     * @param iface The interface.
+     * @param active  True if the interface is actively transmitting data, false if it is idle.
+     */
+    void interfaceClassDataActivityChanged(String label, boolean active);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 06414d9..9253f24 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -121,6 +121,7 @@
 
     <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE" />
     <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE" />
+    <protected-broadcast android:name="android.net.conn.DATA_ACTIVITY_CHANGE" />
 
     <protected-broadcast android:name="android.nfc.action.LLCP_LINK_STATE_CHANGED" />
     <protected-broadcast android:name="com.android.nfc_extras.action.RF_FIELD_ON_DETECTED" />
@@ -508,6 +509,11 @@
         android:permissionGroup="android.permission-group.NETWORK"
         android:protectionLevel="signature|system" />
 
+    <!-- @hide -->
+    <permission android:name="android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"
+        android:permissionGroup="android.permission-group.NETWORK"
+        android:protectionLevel="signature|system" />
+
     <!-- ================================== -->
     <!-- Permissions for accessing accounts -->
     <!-- ================================== -->
diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java
index 9a25d2e..c316733 100644
--- a/services/java/com/android/server/CommonTimeManagementService.java
+++ b/services/java/com/android/server/CommonTimeManagementService.java
@@ -120,6 +120,8 @@
             reevaluateServiceState();
         }
         public void limitReached(String limitName, String iface) { }
+
+        public void interfaceClassDataActivityChanged(String label, boolean active) {}
     };
 
     private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() {
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index 230f07b..9f93901 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -17,6 +17,7 @@
 package com.android.server;
 
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
 import static android.net.ConnectivityManager.isNetworkTypeValid;
@@ -35,6 +36,7 @@
 import android.net.DummyDataStateTracker;
 import android.net.EthernetDataTracker;
 import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
 import android.net.INetworkPolicyListener;
 import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
@@ -546,6 +548,13 @@
         mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
         mSettingsObserver.observe(mContext);
 
+        INetworkManagementEventObserver netdObserver = new NetdObserver();
+        try {
+            mNetd.registerObserver(netdObserver);
+        } catch (RemoteException e) {
+            loge("Error registering observer :" + e);
+        }
+
         loadGlobalProxy();
     }
 private NetworkStateTracker makeWimaxStateTracker() {
@@ -923,6 +932,19 @@
         return tracker != null && tracker.setRadio(turnOn);
     }
 
+    private class NetdObserver extends INetworkManagementEventObserver.Stub {
+        public void interfaceClassDataActivityChanged(String label, boolean active) {
+            int deviceType = Integer.parseInt(label);
+            sendDataActivityBroadcast(deviceType, active);
+        }
+
+        public void interfaceStatusChanged(String iface, boolean up) {}
+        public void interfaceLinkStateChanged(String iface, boolean up) {}
+        public void interfaceAdded(String iface) {}
+        public void interfaceRemoved(String iface) {}
+        public void limitReached(String limitName, String iface) {}
+    }
+
     /**
      * Used to notice when the calling process dies so we can self-expire
      *
@@ -1759,6 +1781,13 @@
         sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
     }
 
+    private void sendDataActivityBroadcast(int deviceType, boolean active) {
+        Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
+        intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
+        intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
+        mContext.sendOrderedBroadcast(intent, RECEIVE_DATA_ACTIVITY_CHANGE);
+    }
+
     /**
      * Called when an attempt to fail over to another network has failed.
      * @param info the {@link NetworkInfo} for the failed network
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 67bb680..c3f3a5d 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -280,6 +280,20 @@
     }
 
     /**
+     * Notify our observers of a change in the data activity state of the interface
+     */
+    private void notifyInterfaceClassActivity(String label, boolean active) {
+        final int length = mObservers.beginBroadcast();
+        for (int i = 0; i < length; i++) {
+            try {
+                mObservers.getBroadcastItem(i).interfaceClassDataActivityChanged(label, active);
+            } catch (RemoteException e) {
+            }
+        }
+        mObservers.finishBroadcast();
+    }
+
+    /**
      * Prepare native daemon once connected, enabling modules and pushing any
      * existing in-memory rules.
      */
@@ -405,6 +419,19 @@
                     throw new IllegalStateException(
                             String.format("Invalid event from daemon (%s)", raw));
                     // break;
+            case NetdResponseCode.InterfaceClassActivity:
+                    /*
+                     * An network interface class state changed (active/idle)
+                     * Format: "NNN IfaceClass <active/idle> <label>"
+                     */
+                    if (cooked.length < 4 || !cooked[1].equals("IfaceClass")) {
+                        throw new IllegalStateException(
+                                String.format("Invalid event from daemon (%s)", raw));
+                    }
+                    boolean isActive = cooked[2].equals("active");
+                    notifyInterfaceClassActivity(cooked[3], isActive);
+                    return true;
+                    // break;
             default: break;
             }
             return false;
diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java
index f35a5af..98e6dc0 100644
--- a/services/java/com/android/server/ThrottleService.java
+++ b/services/java/com/android/server/ThrottleService.java
@@ -195,6 +195,7 @@
 
         public void interfaceRemoved(String iface) {}
         public void limitReached(String limitName, String iface) {}
+        public void interfaceClassDataActivityChanged(String label, boolean active) {}
     }
 
 
diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java
index 5aa3a29..e4f1f7a 100644
--- a/services/java/com/android/server/connectivity/Tethering.java
+++ b/services/java/com/android/server/connectivity/Tethering.java
@@ -319,6 +319,8 @@
 
     public void limitReached(String limitName, String iface) {}
 
+    public void interfaceClassDataActivityChanged(String label, boolean active) {}
+
     public int tether(String iface) {
         if (DBG) Log.d(TAG, "Tethering " + iface);
         TetherInterfaceSM sm = null;
diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java
index 4b82037..b12d597 100644
--- a/services/java/com/android/server/connectivity/Vpn.java
+++ b/services/java/com/android/server/connectivity/Vpn.java
@@ -291,6 +291,9 @@
     public void limitReached(String limit, String interfaze) {
     }
 
+    public void interfaceClassDataActivityChanged(String label, boolean active) {
+    }
+
     private void enforceControlPermission() {
         // System user is allowed to control VPN.
         if (Binder.getCallingUid() == Process.SYSTEM_UID) {
diff --git a/services/java/com/android/server/net/NetworkAlertObserver.java b/services/java/com/android/server/net/NetworkAlertObserver.java
index 0d1c3b2..9e181c5 100644
--- a/services/java/com/android/server/net/NetworkAlertObserver.java
+++ b/services/java/com/android/server/net/NetworkAlertObserver.java
@@ -41,4 +41,7 @@
     public void interfaceAdded(String iface) {
         // ignored; interface changes come through ConnectivityService
     }
+    public void interfaceClassDataActivityChanged(String label, boolean active) {
+        // ignored; interface changes come through ConnectivityService
+    }
 }