[DPM] Management and retrieval of network logs

This CL follows up on ag/1530343 and adds:
1) Various network events.
2) Retrieval method in DPM and APIs in DeviceAdminReceiver.
3) Extension of NetworkLogger and it's NetworkLoggingHandler.

Test: runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
Bug: 29748723
Change-Id: I42a1a477e7c75c109a3982f809c22732b814e8b2
diff --git a/core/java/android/app/admin/ConnectEvent.aidl b/core/java/android/app/admin/ConnectEvent.aidl
new file mode 100644
index 0000000..bab40f5
--- /dev/null
+++ b/core/java/android/app/admin/ConnectEvent.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 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.app.admin;
+
+/** {@hide} */
+parcelable ConnectEvent;
+
diff --git a/core/java/android/app/admin/ConnectEvent.java b/core/java/android/app/admin/ConnectEvent.java
new file mode 100644
index 0000000..e05feaf
--- /dev/null
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 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.app.admin;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents a connect library call event.
+ * @hide
+ */
+public final class ConnectEvent extends NetworkEvent implements Parcelable {
+
+    /** The destination IP address. */
+    private final String ipAddress;
+
+    /** The destination port number. */
+    private final int port;
+
+    public ConnectEvent(String ipAddress, int port, String packageName, long timestamp) {
+        super(packageName, timestamp);
+        this.ipAddress = ipAddress;
+        this.port = port;
+    }
+
+    private ConnectEvent(Parcel in) {
+        this.ipAddress = in.readString();
+        this.port = in.readInt();
+        this.packageName = in.readString();
+        this.timestamp = in.readLong();
+    }
+
+    public String getIpAddress() {
+        return ipAddress;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("ConnectEvent(%s, %d, %d, %s)", ipAddress, port, timestamp,
+                packageName);
+    }
+
+    public static final Parcelable.Creator<ConnectEvent> CREATOR
+            = new Parcelable.Creator<ConnectEvent>() {
+        @Override
+        public ConnectEvent createFromParcel(Parcel in) {
+            if (in.readInt() != PARCEL_TOKEN_CONNECT_EVENT) {
+                return null;
+            }
+            return new ConnectEvent(in);
+        }
+
+        @Override
+        public ConnectEvent[] newArray(int size) {
+            return new ConnectEvent[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        // write parcel token first
+        out.writeInt(PARCEL_TOKEN_CONNECT_EVENT);
+        out.writeString(ipAddress);
+        out.writeInt(port);
+        out.writeString(packageName);
+        out.writeLong(timestamp);
+    }
+}
+
diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java
index dd70b5d..360087c 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -276,6 +276,15 @@
             = "android.app.action.SECURITY_LOGS_AVAILABLE";
 
     /**
+     * Broadcast action: notify that a new batch of network logs is ready to be collected.
+     * @see DeviceAdminReceiver#onNetworkLogsAvailable
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NETWORK_LOGS_AVAILABLE
+            = "android.app.action.NETWORK_LOGS_AVAILABLE";
+
+    /**
      * A string containing the SHA-256 hash of the bugreport file.
      *
      * @see #ACTION_BUGREPORT_SHARE
@@ -635,6 +644,22 @@
     }
 
     /**
+     * Called when a new batch of network logs can be retrieved. This callback method will only ever
+     * be called when network logging is enabled. The logs can only be retrieved while network
+     * logging is enabled.
+     *
+     * <p>This callback is only applicable to device owners.
+     *
+     * @param context The running context as per {@link #onReceive}.
+     * @param intent The received intent as per {@link #onReceive}.
+     * @see DevicePolicyManager#retrieveNetworkLogs(ComponentName)
+     *
+     * @hide
+     */
+    public void onNetworkLogsAvailable(Context context, Intent intent) {
+    }
+
+    /**
      * Intercept standard device administrator broadcasts.  Implementations
      * should not override this method; it is better to implement the
      * convenience callbacks for each action.
@@ -688,6 +713,8 @@
             onBugreportFailed(context, intent, failureCode);
         } else if (ACTION_SECURITY_LOGS_AVAILABLE.equals(action)) {
             onSecurityLogsAvailable(context, intent);
+        } else if (ACTION_NETWORK_LOGS_AVAILABLE.equals(action)) {
+            onNetworkLogsAvailable(context, intent);
         }
     }
 }
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 138ec02..94cfaca 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -26,6 +26,7 @@
 import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.Activity;
+import android.app.admin.NetworkEvent;
 import android.app.admin.PasswordMetrics;
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.content.ComponentName;
@@ -6648,6 +6649,7 @@
      * @throws {@link SecurityException} if {@code admin} is not a device owner.
      * @throws {@link RemoteException} if network logging could not be enabled or disabled due to
      *         the logging service not being available
+     * @see #retrieveNetworkLogs
      *
      * @hide
      */
@@ -6677,4 +6679,31 @@
             throw re.rethrowFromSystemServer();
         }
     }
+
+    /**
+     * Called by device owner to retrieve a new batch of network logging events.
+     *
+     * <p> {@link NetworkEvent} can be one of {@link DnsEvent} or {@link ConnectEvent}.
+     *
+     * <p> The list of network events is sorted chronologically, and contains at most 1200 events.
+     *
+     * <p> Access to the logs is rate limited and this method will only return a new batch of logs
+     * after the device device owner has been notified via
+     * {@link DeviceAdminReceiver#onNetworkLogsAvailable}.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+     * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
+     * {@code null} if there's no batch currently awaiting for retrieval or if logging is disabled.
+     * @throws {@link SecurityException} if {@code admin} is not a device owner.
+     *
+     * @hide
+     */
+    public List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin) {
+        throwIfParentInstance("retrieveNetworkLogs");
+        try {
+            return mService.retrieveNetworkLogs(admin);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
 }
diff --git a/core/java/android/app/admin/DnsEvent.aidl b/core/java/android/app/admin/DnsEvent.aidl
new file mode 100644
index 0000000..6da962a
--- /dev/null
+++ b/core/java/android/app/admin/DnsEvent.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 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.app.admin;
+
+/** {@hide} */
+parcelable DnsEvent;
+
diff --git a/core/java/android/app/admin/DnsEvent.java b/core/java/android/app/admin/DnsEvent.java
new file mode 100644
index 0000000..0ec134a
--- /dev/null
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.app.admin;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A class that represents a DNS lookup event.
+ * @hide
+ */
+public final class DnsEvent extends NetworkEvent implements Parcelable {
+
+    /** The hostname that was looked up. */
+    private final String hostname;
+
+    /** Contains (possibly a subset of) the IP addresses returned. */
+    private final String[] ipAddresses;
+
+    /**
+     * The number of IP addresses returned from the DNS lookup event. May be different from the
+     * length of ipAddresses if there were too many addresses to log.
+     */
+    private final int ipAddressesCount;
+
+    public DnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
+            String packageName, long timestamp) {
+        super(packageName, timestamp);
+        this.hostname = hostname;
+        this.ipAddresses = ipAddresses;
+        this.ipAddressesCount = ipAddressesCount;
+    }
+
+    private DnsEvent(Parcel in) {
+        this.hostname = in.readString();
+        this.ipAddresses = in.createStringArray();
+        this.ipAddressesCount = in.readInt();
+        this.packageName = in.readString();
+        this.timestamp = in.readLong();
+    }
+
+    /** Returns the hostname that was looked up. */
+    public String getHostname() {
+        return hostname;
+    }
+
+    /** Returns (possibly a subset of) the IP addresses returned. */
+    public String[] getIpAddresses() {
+        return ipAddresses;
+    }
+
+    /**
+     * Returns the number of IP addresses returned from the DNS lookup event. May be different from
+     * the length of ipAddresses if there were too many addresses to log.
+     */
+    public int getIpAddressesCount() {
+        return ipAddressesCount;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("DnsEvent(%s, %s, %d, %d, %s)", hostname,
+                (ipAddresses == null) ? "NONE" : String.join(" ", ipAddresses),
+                ipAddressesCount, timestamp, packageName);
+    }
+
+    public static final Parcelable.Creator<DnsEvent> CREATOR
+            = new Parcelable.Creator<DnsEvent>() {
+        @Override
+        public DnsEvent createFromParcel(Parcel in) {
+            if (in.readInt() != PARCEL_TOKEN_DNS_EVENT) {
+                return null;
+            }
+            return new DnsEvent(in);
+        }
+
+        @Override
+        public DnsEvent[] newArray(int size) {
+            return new DnsEvent[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        // write parcel token first
+        out.writeInt(PARCEL_TOKEN_DNS_EVENT);
+        out.writeString(hostname);
+        out.writeStringArray(ipAddresses);
+        out.writeInt(ipAddressesCount);
+        out.writeString(packageName);
+        out.writeLong(timestamp);
+    }
+}
+
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 3cfa1e8..b0aec8c 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -17,6 +17,7 @@
 
 package android.app.admin;
 
+import android.app.admin.NetworkEvent;
 import android.app.admin.SystemUpdatePolicy;
 import android.app.admin.PasswordMetrics;
 import android.content.ComponentName;
@@ -317,4 +318,5 @@
 
     void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled);
     boolean isNetworkLoggingEnabled(in ComponentName admin);
+    List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin);
 }
diff --git a/core/java/android/app/admin/NetworkEvent.aidl b/core/java/android/app/admin/NetworkEvent.aidl
new file mode 100644
index 0000000..5fa5dbf
--- /dev/null
+++ b/core/java/android/app/admin/NetworkEvent.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 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.app.admin;
+
+/** {@hide} */
+parcelable NetworkEvent;
+
diff --git a/core/java/android/app/admin/NetworkEvent.java b/core/java/android/app/admin/NetworkEvent.java
new file mode 100644
index 0000000..ec7ed00
--- /dev/null
+++ b/core/java/android/app/admin/NetworkEvent.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 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.app.admin;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.ParcelFormatException;
+
+/**
+ * An abstract class that represents a network event.
+ * @hide
+ */
+public abstract class NetworkEvent implements Parcelable {
+
+    protected static final int PARCEL_TOKEN_DNS_EVENT = 1;
+    protected static final int PARCEL_TOKEN_CONNECT_EVENT = 2;
+
+    /** The package name of the UID that performed the query. */
+    protected String packageName;
+
+    /** The timestamp of the event being reported in milliseconds. */
+    protected long timestamp;
+
+    protected NetworkEvent() {
+        //empty constructor
+    }
+
+    protected NetworkEvent(String packageName, long timestamp) {
+        this.packageName = packageName;
+        this.timestamp = timestamp;
+    }
+
+    /** Returns the package name of the UID that performed the query. */
+    public String getPackageName() {
+        return packageName;
+    }
+
+    /** Returns the timestamp of the event being reported in milliseconds. */
+    public long getTimestamp() {
+        return timestamp;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<NetworkEvent> CREATOR
+            = new Parcelable.Creator<NetworkEvent>() {
+        public NetworkEvent createFromParcel(Parcel in) {
+            final int initialPosition = in.dataPosition();
+            final int parcelToken = in.readInt();
+            // we need to move back to the position from before we read parcelToken
+            in.setDataPosition(initialPosition);
+            switch (parcelToken) {
+                case PARCEL_TOKEN_DNS_EVENT:
+                    return DnsEvent.CREATOR.createFromParcel(in);
+                case PARCEL_TOKEN_CONNECT_EVENT:
+                    return ConnectEvent.CREATOR.createFromParcel(in);
+                default:
+                    throw new ParcelFormatException("Unexpected NetworkEvent token in parcel: "
+                            + parcelToken);
+            }
+        }
+
+        public NetworkEvent[] newArray(int size) {
+            return new NetworkEvent[size];
+        }
+    };
+}
+
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index da4eb2d..1f013ae 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -189,4 +189,17 @@
     public abstract void revokeRuntimePermission(String packageName, String name, int userId,
             boolean overridePolicy);
 
+    /**
+     * Retrieve the official name associated with a user id.  This name is
+     * guaranteed to never change, though it is possible for the underlying
+     * user id to be changed.  That is, if you are storing information about
+     * user ids in persistent storage, you should use the string returned
+     * by this function instead of the raw user-id.
+     *
+     * @param uid The user id for which you would like to retrieve a name.
+     * @return Returns a unique name for the given user id, or null if the
+     * user id is not currently assigned.
+     */
+    public abstract String getNameForUid(int uid);
+
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index b38bb1e..7f657ea 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -454,6 +454,7 @@
     <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" />
     <protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" />
     <protected-broadcast android:name="android.intent.action.MEDIA_RESOURCE_GRANTED" />
+    <protected-broadcast android:name="android.app.action.NETWORK_LOGS_AVAILABLE" />
     <protected-broadcast android:name="android.app.action.SECURITY_LOGS_AVAILABLE" />
 
     <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED" />