summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk4
-rw-r--r--core/java/android/app/admin/ConnectEvent.aidl21
-rw-r--r--core/java/android/app/admin/ConnectEvent.java93
-rw-r--r--core/java/android/app/admin/DeviceAdminReceiver.java53
-rw-r--r--core/java/android/app/admin/DevicePolicyManager.java76
-rw-r--r--core/java/android/app/admin/DnsEvent.aidl21
-rw-r--r--core/java/android/app/admin/DnsEvent.java124
-rw-r--r--core/java/android/app/admin/IDevicePolicyManager.aidl5
-rw-r--r--core/java/android/app/admin/NetworkEvent.aidl21
-rw-r--r--core/java/android/app/admin/NetworkEvent.java97
-rw-r--r--core/java/android/content/pm/PackageManagerInternal.java13
-rw-r--r--core/java/android/net/IIpConnectivityMetrics.aidl10
-rw-r--r--core/java/android/net/INetdEventCallback.aidl47
-rw-r--r--core/res/AndroidManifest.xml1
-rw-r--r--services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java30
-rw-r--r--services/core/java/com/android/server/connectivity/NetdEventListenerService.java31
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java5
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java145
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java153
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java124
-rw-r--r--services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java11
-rw-r--r--services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java62
22 files changed, 1138 insertions, 9 deletions
diff --git a/Android.mk b/Android.mk
index d813c9190e62..730eeb111ac7 100644
--- a/Android.mk
+++ b/Android.mk
@@ -202,6 +202,7 @@ LOCAL_SRC_FILES += \
core/java/android/net/IIpConnectivityMetrics.aidl \
core/java/android/net/IEthernetManager.aidl \
core/java/android/net/IEthernetServiceListener.aidl \
+ core/java/android/net/INetdEventCallback.aidl \
core/java/android/net/INetworkManagementEventObserver.aidl \
core/java/android/net/INetworkPolicyListener.aidl \
core/java/android/net/INetworkPolicyManager.aidl \
@@ -574,6 +575,9 @@ aidl_files := \
frameworks/base/graphics/java/android/graphics/drawable/Icon.aidl \
frameworks/base/core/java/android/accounts/AuthenticatorDescription.aidl \
frameworks/base/core/java/android/accounts/Account.aidl \
+ frameworks/base/core/java/android/app/admin/ConnectEvent.aidl \
+ frameworks/base/core/java/android/app/admin/DnsEvent.aidl \
+ frameworks/base/core/java/android/app/admin/NetworkEvent.aidl \
frameworks/base/core/java/android/app/admin/SystemUpdatePolicy.aidl \
frameworks/base/core/java/android/print/PrintDocumentInfo.aidl \
frameworks/base/core/java/android/print/PageRange.aidl \
diff --git a/core/java/android/app/admin/ConnectEvent.aidl b/core/java/android/app/admin/ConnectEvent.aidl
new file mode 100644
index 000000000000..bab40f5add38
--- /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 000000000000..b6b14f24a096
--- /dev/null
+++ b/core/java/android/app/admin/ConnectEvent.java
@@ -0,0 +1,93 @@
+/*
+ * 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;
+
+ /** @hide */
+ 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 int describeContents() {
+ return 0;
+ }
+
+ @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 dd70b5dfd1f0..cbd5a6d54832 100644
--- a/core/java/android/app/admin/DeviceAdminReceiver.java
+++ b/core/java/android/app/admin/DeviceAdminReceiver.java
@@ -276,6 +276,36 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
= "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 {@code long} containing a token of the current batch of network logs, that has to be used
+ * to retrieve the batch of logs by the device owner.
+ *
+ * @see #ACTION_NETWORK_LOGS_AVAILABLE
+ * @see DevicePolicyManager#retrieveNetworkLogs
+ * @hide
+ */
+ public static final String EXTRA_NETWORK_LOGS_TOKEN =
+ "android.app.extra.EXTRA_NETWORK_LOGS_TOKEN";
+
+ /**
+ * An {@code int} count representing a total count of network logs inside the current batch of
+ * network logs.
+ *
+ * @see #ACTION_NETWORK_LOGS_AVAILABLE
+ * @hide
+ */
+ public static final String EXTRA_NETWORK_LOGS_COUNT =
+ "android.app.extra.EXTRA_NETWORK_LOGS_COUNT";
+
+ /**
* A string containing the SHA-256 hash of the bugreport file.
*
* @see #ACTION_BUGREPORT_SHARE
@@ -635,6 +665,25 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
}
/**
+ * Called each time 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}.
+ * @param batchToken The token representing the current batch of network logs.
+ * @param networkLogsCount The total count of events in the current batch of network logs.
+ * @see DevicePolicyManager#retrieveNetworkLogs(ComponentName)
+ *
+ * @hide
+ */
+ public void onNetworkLogsAvailable(Context context, Intent intent, long batchToken,
+ int networkLogsCount) {
+ }
+
+ /**
* Intercept standard device administrator broadcasts. Implementations
* should not override this method; it is better to implement the
* convenience callbacks for each action.
@@ -688,6 +737,10 @@ public class DeviceAdminReceiver extends BroadcastReceiver {
onBugreportFailed(context, intent, failureCode);
} else if (ACTION_SECURITY_LOGS_AVAILABLE.equals(action)) {
onSecurityLogsAvailable(context, intent);
+ } else if (ACTION_NETWORK_LOGS_AVAILABLE.equals(action)) {
+ long batchToken = intent.getLongExtra(EXTRA_NETWORK_LOGS_TOKEN, -1);
+ int networkLogsCount = intent.getIntExtra(EXTRA_NETWORK_LOGS_COUNT, 0);
+ onNetworkLogsAvailable(context, intent, batchToken, networkLogsCount);
}
}
}
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index a2f9bdd2370d..c2197b55ef97 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -25,6 +25,7 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.UserIdInt;
import android.app.Activity;
+import android.app.admin.NetworkEvent;
import android.app.admin.SecurityLog.SecurityEvent;
import android.content.ComponentName;
import android.content.Context;
@@ -6609,4 +6610,79 @@ public class DevicePolicyManager {
throw re.rethrowFromSystemServer();
}
}
+
+ /**
+ * Called by a device owner to control the network logging feature. Logging can only be
+ * enabled on single user devices where the sole user is managed by the device owner. If a new
+ * user is added on the device, logging is disabled.
+ *
+ * <p> Network logs contain DNS lookup and connect() library call events.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param enabled whether network logging should be enabled or not.
+ * @throws {@link SecurityException} if {@code admin} is not a device owner.
+ * @see #retrieveNetworkLogs
+ *
+ * @hide
+ */
+ public void setNetworkLoggingEnabled(@NonNull ComponentName admin, boolean enabled) {
+ throwIfParentInstance("setNetworkLoggingEnabled");
+ try {
+ mService.setNetworkLoggingEnabled(admin, enabled);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Return whether network logging is enabled by a device owner.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @return {@code true} if network logging is enabled by device owner, {@code false} otherwise.
+ * @throws {@link SecurityException} if {@code admin} is not a device owner.
+ *
+ * @hide
+ */
+ public boolean isNetworkLoggingEnabled(@NonNull ComponentName admin) {
+ throwIfParentInstance("isNetworkLoggingEnabled");
+ try {
+ return mService.isNetworkLoggingEnabled(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Called by device owner to retrieve the most recent batch of network logging events.
+ * A device owner has to provide a batchToken provided as part of
+ * {@link DeviceAdminReceiver#onNetworkLogsAvailable} callback. If the token doesn't match the
+ * token of the most recent available batch of logs, {@code null} will be returned.
+ *
+ * <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.
+ * @param batchToken A token of the batch to retrieve
+ * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns
+ * {@code null} if the batch represented by batchToken is no longer available or if
+ * logging is disabled.
+ * @throws {@link SecurityException} if {@code admin} is not a device owner.
+ * @see DeviceAdminReceiver#onNetworkLogsAvailable
+ *
+ * @hide
+ */
+ public @Nullable List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin,
+ long batchToken) {
+ throwIfParentInstance("retrieveNetworkLogs");
+ try {
+ return mService.retrieveNetworkLogs(admin, batchToken);
+ } 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 000000000000..6da962a0a828
--- /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 000000000000..4ff8e1cba531
--- /dev/null
+++ b/core/java/android/app/admin/DnsEvent.java
@@ -0,0 +1,124 @@
+/*
+ * 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;
+
+ /** @hide */
+ 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() {
+ StringBuilder sb = new StringBuilder();
+ if (ipAddresses != null) {
+ for (int i = 0; i < ipAddresses.length; i++) {
+ sb.append(ipAddresses[i]);
+ if (i < ipAddresses.length - 1) {
+ sb.append(" ");
+ }
+ }
+ } else {
+ sb.append("NONE");
+ }
+ return String.format("DnsEvent(%s, %s, %d, %d, %s)", hostname, sb.toString(),
+ 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 int describeContents() {
+ return 0;
+ }
+
+ @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 f39cb5ae9fbd..f0710ec269eb 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.content.ComponentName;
import android.content.Intent;
@@ -311,4 +312,8 @@ interface IDevicePolicyManager {
void setBackupServiceEnabled(in ComponentName admin, boolean enabled);
boolean isBackupServiceEnabled(in ComponentName admin);
+
+ void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled);
+ boolean isNetworkLoggingEnabled(in ComponentName admin);
+ List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin, long batchToken);
}
diff --git a/core/java/android/app/admin/NetworkEvent.aidl b/core/java/android/app/admin/NetworkEvent.aidl
new file mode 100644
index 000000000000..5fa5dbfabb5f
--- /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 000000000000..0de2665f90a8
--- /dev/null
+++ b/core/java/android/app/admin/NetworkEvent.java
@@ -0,0 +1,97 @@
+/*
+ * 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.content.pm.PackageManager;
+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 {
+
+ static final int PARCEL_TOKEN_DNS_EVENT = 1;
+ static final int PARCEL_TOKEN_CONNECT_EVENT = 2;
+
+ /** The package name of the UID that performed the query. */
+ String packageName;
+
+ /** The timestamp of the event being reported in milliseconds. */
+ long timestamp;
+
+ /** @hide */
+ NetworkEvent() {
+ //empty constructor
+ }
+
+ /** @hide */
+ NetworkEvent(String packageName, long timestamp) {
+ this.packageName = packageName;
+ this.timestamp = timestamp;
+ }
+
+ /**
+ * Returns the package name of the UID that performed the query, as returned by
+ * {@link PackageManager#getNameForUid}.
+ */
+ public String getPackageName() {
+ return packageName;
+ }
+
+ /**
+ * Returns the timestamp of the event being reported in milliseconds, the difference between
+ * the time the event was reported and midnight, January 1, 1970 UTC.
+ */
+ 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];
+ }
+ };
+
+ @Override
+ public abstract void writeToParcel(Parcel out, int flags);
+}
+
diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java
index f5bcf64417a6..9bb212886861 100644
--- a/core/java/android/content/pm/PackageManagerInternal.java
+++ b/core/java/android/content/pm/PackageManagerInternal.java
@@ -168,4 +168,17 @@ public abstract class PackageManagerInternal {
* @return Whether was launched.
*/
public abstract boolean wasPackageEverLaunched(String packageName, int userId);
+
+ /**
+ * 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/java/android/net/IIpConnectivityMetrics.aidl b/core/java/android/net/IIpConnectivityMetrics.aidl
index d36b7661aaa3..6f07b3153833 100644
--- a/core/java/android/net/IIpConnectivityMetrics.aidl
+++ b/core/java/android/net/IIpConnectivityMetrics.aidl
@@ -18,6 +18,7 @@ package android.net;
import android.os.Parcelable;
import android.net.ConnectivityMetricsEvent;
+import android.net.INetdEventCallback;
/** {@hide} */
interface IIpConnectivityMetrics {
@@ -27,4 +28,13 @@ interface IIpConnectivityMetrics {
* or -1 if the event was dropped due to rate limiting.
*/
int logEvent(in ConnectivityMetricsEvent event);
+
+ /**
+ * At most one callback can be registered (by DevicePolicyManager).
+ * @return status {@code true} if registering/unregistering of the callback was successful,
+ * {@code false} otherwise (might happen if IIpConnectivityMetrics is not available,
+ * if it happens make sure you call it when the service is up in the caller)
+ */
+ boolean registerNetdEventCallback(in INetdEventCallback callback);
+ boolean unregisterNetdEventCallback();
}
diff --git a/core/java/android/net/INetdEventCallback.aidl b/core/java/android/net/INetdEventCallback.aidl
new file mode 100644
index 000000000000..49436beadc51
--- /dev/null
+++ b/core/java/android/net/INetdEventCallback.aidl
@@ -0,0 +1,47 @@
+/*
+ * 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.net;
+
+/** {@hide} */
+oneway interface INetdEventCallback {
+
+ /**
+ * Reports a single DNS lookup function call.
+ * This method must not block or perform long-running operations.
+ *
+ * @param hostname the name that was looked up.
+ * @param ipAddresses (possibly a subset of) the IP addresses returned.
+ * At most {@link #DNS_REPORTED_IP_ADDRESSES_LIMIT} addresses are logged.
+ * @param ipAddressesCount the number of IP addresses returned. May be different from the length
+ * of ipAddresses if there were too many addresses to log.
+ * @param timestamp the timestamp at which the query was reported by netd.
+ * @param uid the UID of the application that performed the query.
+ */
+ void onDnsEvent(String hostname, in String[] ipAddresses, int ipAddressesCount, long timestamp,
+ int uid);
+
+ /**
+ * Reports a single connect library call.
+ * This method must not block or perform long-running operations.
+ *
+ * @param ipAddr destination IP address.
+ * @param port destination port number.
+ * @param timestamp the timestamp at which the call was reported by netd.
+ * @param uid the UID of the application that performed the connection.
+ */
+ void onConnectEvent(String ipAddr, int port, long timestamp, int uid);
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index f39fc1add036..36311dcf243f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -443,6 +443,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" />
diff --git a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
index 6654814a0157..f485f56306a4 100644
--- a/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/services/core/java/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -19,10 +19,13 @@ package com.android.server.connectivity;
import android.content.Context;
import android.net.ConnectivityMetricsEvent;
import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
import android.net.metrics.ApfProgramEvent;
import android.net.metrics.IpConnectivityLog;
+import android.os.Binder;
import android.os.IBinder;
import android.os.Parcelable;
+import android.os.Process;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.format.DateUtils;
@@ -260,6 +263,33 @@ final public class IpConnectivityMetrics extends SystemService {
private void enforcePermission(String what) {
getContext().enforceCallingOrSelfPermission(what, "IpConnectivityMetrics");
}
+
+ private void enforceNetdEventListeningPermission() {
+ final int uid = Binder.getCallingUid();
+ if (uid != Process.SYSTEM_UID) {
+ throw new SecurityException(String.format("Uid %d has no permission to listen for"
+ + " netd events.", uid));
+ }
+ }
+
+ @Override
+ public boolean registerNetdEventCallback(INetdEventCallback callback) {
+ enforceNetdEventListeningPermission();
+ if (mNetdListener == null) {
+ return false;
+ }
+ return mNetdListener.registerNetdEventCallback(callback);
+ }
+
+ @Override
+ public boolean unregisterNetdEventCallback() {
+ enforceNetdEventListeningPermission();
+ if (mNetdListener == null) {
+ // if the service is null, we aren't registered anyway
+ return true;
+ }
+ return mNetdListener.unregisterNetdEventCallback();
+ }
};
private static final ToIntFunction<Context> READ_BUFFER_SIZE = (ctx) -> {
diff --git a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
index 4b175d7a5af8..8450482f4032 100644
--- a/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
+++ b/services/core/java/com/android/server/connectivity/NetdEventListenerService.java
@@ -20,10 +20,12 @@ import android.content.Context;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
import android.net.Network;
+import android.net.INetdEventCallback;
import android.net.NetworkRequest;
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
+import android.os.RemoteException;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
@@ -119,6 +121,21 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
}
};
+ // Callback should only be registered/unregistered when logging is being enabled/disabled in DPM
+ // by the device owner. It's DevicePolicyManager's responsibility to ensure that.
+ @GuardedBy("this")
+ private INetdEventCallback mNetdEventCallback;
+
+ public synchronized boolean registerNetdEventCallback(INetdEventCallback callback) {
+ mNetdEventCallback = callback;
+ return true;
+ }
+
+ public synchronized boolean unregisterNetdEventCallback() {
+ mNetdEventCallback = null;
+ return true;
+ }
+
public NetdEventListenerService(Context context) {
this(context.getSystemService(ConnectivityManager.class), new IpConnectivityLog());
}
@@ -136,7 +153,8 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
// Called concurrently by multiple binder threads.
// This method must not block or perform long-running operations.
public synchronized void onDnsEvent(int netId, int eventType, int returnCode, int latencyMs,
- String hostname, String[] ipAddresses, int ipAddressesCount, int uid) {
+ String hostname, String[] ipAddresses, int ipAddressesCount, int uid)
+ throws RemoteException {
maybeVerboseLog(String.format("onDnsEvent(%d, %d, %d, %d)",
netId, eventType, returnCode, latencyMs));
@@ -146,14 +164,23 @@ public class NetdEventListenerService extends INetdEventListener.Stub {
mEventBatches.put(netId, batch);
}
batch.addResult((byte) eventType, (byte) returnCode, latencyMs);
+
+ if (mNetdEventCallback != null) {
+ mNetdEventCallback.onDnsEvent(hostname, ipAddresses, ipAddressesCount,
+ System.currentTimeMillis(), uid);
+ }
}
@Override
// Called concurrently by multiple binder threads.
// This method must not block or perform long-running operations.
public synchronized void onConnectEvent(int netId, int latencyMs, String ipAddr, int port,
- int uid) {
+ int uid) throws RemoteException {
maybeVerboseLog(String.format("onConnectEvent(%d, %d)", netId, latencyMs));
+
+ if (mNetdEventCallback != null) {
+ mNetdEventCallback.onConnectEvent(ipAddr, port, System.currentTimeMillis(), uid);
+ }
}
public synchronized void dump(PrintWriter writer) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 3bd533d03628..d2db59d9acd0 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -21099,6 +21099,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
return mSettings.wasPackageEverLaunchedLPr(packageName, userId);
}
}
+
+ @Override
+ public String getNameForUid(int uid) {
+ return PackageManagerService.this.getNameForUid(uid);
+ }
}
@Override
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 96331e83dd31..232300a2e70c 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -50,6 +50,7 @@ import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.IDevicePolicyManager;
+import android.app.admin.NetworkEvent;
import android.app.admin.SecurityLog;
import android.app.admin.SecurityLog.SecurityEvent;
import android.app.admin.SystemUpdatePolicy;
@@ -77,8 +78,10 @@ import android.graphics.Color;
import android.media.AudioManager;
import android.media.IAudioService;
import android.net.ConnectivityManager;
+import android.net.IIpConnectivityMetrics;
import android.net.ProxyInfo;
import android.net.Uri;
+import android.net.metrics.IpConnectivityLog;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
@@ -352,6 +355,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
boolean mIsWatch;
private final SecurityLogMonitor mSecurityLogMonitor;
+ private NetworkLogger mNetworkLogger;
private final AtomicBoolean mRemoteBugreportServiceIsActive = new AtomicBoolean();
private final AtomicBoolean mRemoteBugreportSharingAccepted = new AtomicBoolean();
@@ -478,6 +482,19 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
final int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
getSendingUserId());
+ /*
+ * Network logging would ideally be started in setDeviceOwnerSystemPropertyLocked(),
+ * however it's too early in the boot process to register with IIpConnectivityMetrics
+ * to listen for events.
+ */
+ if (Intent.ACTION_USER_STARTED.equals(action)
+ && userHandle == mOwners.getDeviceOwnerUserId()) {
+ synchronized (DevicePolicyManagerService.this) {
+ if (isNetworkLoggingEnabledInternalLocked()) {
+ setNetworkLoggingActiveInternal(true);
+ }
+ }
+ }
if (Intent.ACTION_BOOT_COMPLETED.equals(action)
&& userHandle == mOwners.getDeviceOwnerUserId()
&& getDeviceOwnerRemoteBugreportUri() != null) {
@@ -549,6 +566,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
private static final String TAG_DISABLE_ACCOUNT_MANAGEMENT = "disable-account-management";
private static final String TAG_REQUIRE_AUTO_TIME = "require_auto_time";
private static final String TAG_FORCE_EPHEMERAL_USERS = "force_ephemeral_users";
+ private static final String TAG_IS_NETWORK_LOGGING_ENABLED = "is_network_logging_enabled";
private static final String TAG_ACCOUNT_TYPE = "account-type";
private static final String TAG_PERMITTED_ACCESSIBILITY_SERVICES
= "permitted-accessiblity-services";
@@ -643,6 +661,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
boolean disableScreenCapture = false; // Can only be set by a device/profile owner.
boolean requireAutoTime = false; // Can only be set by a device owner.
boolean forceEphemeralUsers = false; // Can only be set by a device owner.
+ boolean isNetworkLoggingEnabled = false; // Can only be set by a device owner.
ActiveAdmin parentAdmin;
final boolean isParent;
@@ -851,6 +870,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
out.attribute(null, ATTR_VALUE, Boolean.toString(forceEphemeralUsers));
out.endTag(null, TAG_FORCE_EPHEMERAL_USERS);
}
+ if (isNetworkLoggingEnabled) {
+ out.startTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
+ out.attribute(null, ATTR_VALUE, Boolean.toString(isNetworkLoggingEnabled));
+ out.endTag(null, TAG_IS_NETWORK_LOGGING_ENABLED);
+ }
if (disabledKeyguardFeatures != DEF_KEYGUARD_FEATURES_DISABLED) {
out.startTag(null, TAG_DISABLE_KEYGUARD_FEATURES);
out.attribute(null, ATTR_VALUE, Integer.toString(disabledKeyguardFeatures));
@@ -1037,6 +1061,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
} else if (TAG_FORCE_EPHEMERAL_USERS.equals(tag)) {
forceEphemeralUsers = Boolean.parseBoolean(
parser.getAttributeValue(null, ATTR_VALUE));
+ } else if (TAG_IS_NETWORK_LOGGING_ENABLED.equals(tag)) {
+ isNetworkLoggingEnabled = Boolean.parseBoolean(
+ parser.getAttributeValue(null, ATTR_VALUE));
} else if (TAG_DISABLE_KEYGUARD_FEATURES.equals(tag)) {
disabledKeyguardFeatures = Integer.parseInt(
parser.getAttributeValue(null, ATTR_VALUE));
@@ -1277,6 +1304,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
pw.println(requireAutoTime);
pw.print(prefix); pw.print("forceEphemeralUsers=");
pw.println(forceEphemeralUsers);
+ pw.print(prefix); pw.print("isNetworkLoggingEnabled=");
+ pw.println(isNetworkLoggingEnabled);
pw.print(prefix); pw.print("disabledKeyguardFeatures=");
pw.println(disabledKeyguardFeatures);
pw.print(prefix); pw.print("crossProfileWidgetProviders=");
@@ -1403,6 +1432,11 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
return mContext.getSystemService(NotificationManager.class);
}
+ IIpConnectivityMetrics getIIpConnectivityMetrics() {
+ return (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface(
+ ServiceManager.getService(IpConnectivityLog.SERVICE_NAME));
+ }
+
PowerManagerInternal getPowerManagerInternal() {
return LocalServices.getService(PowerManagerInternal.class);
}
@@ -9037,12 +9071,33 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
private synchronized void disableDeviceOwnerManagedSingleUserFeaturesIfNeeded() {
- if (!isDeviceOwnerManagedSingleUserDevice()) {
+ final boolean isSingleUserManagedDevice = isDeviceOwnerManagedSingleUserDevice();
+
+ // disable security logging if needed
+ if (!isSingleUserManagedDevice) {
mInjector.securityLogSetLoggingEnabledProperty(false);
- Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user device.");
+ Slog.w(LOG_TAG, "Security logging turned off as it's no longer a single user managed"
+ + " device.");
+ }
+
+ // disable backup service if needed
+ // note: when clearing DO, the backup service shouldn't be disabled if it was enabled by
+ // the device owner
+ if (mOwners.hasDeviceOwner() && !isSingleUserManagedDevice) {
+ setBackupServiceEnabledInternal(false);
+ Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
+ }
+
+ // disable network logging if needed
+ if (!isSingleUserManagedDevice) {
+ setNetworkLoggingActiveInternal(false);
+ Slog.w(LOG_TAG, "Network logging turned off as it's no longer a single user managed"
+ + " device.");
+ // if there still is a device owner, disable logging policy, otherwise the admin
+ // has been nuked
if (mOwners.hasDeviceOwner()) {
- setBackupServiceEnabledInternal(false);
- Slog.w(LOG_TAG, "Backup is off as it's a managed device that has more that one user.");
+ getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = false;
+ saveSettingsLocked(mOwners.getDeviceOwnerUserId());
}
}
}
@@ -9386,4 +9441,86 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
}
}
}
+
+ @Override
+ public synchronized void setNetworkLoggingEnabled(ComponentName admin, boolean enabled) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(admin);
+ ensureDeviceOwnerManagingSingleUser(admin);
+
+ if (enabled == isNetworkLoggingEnabledInternalLocked()) {
+ // already in the requested state
+ return;
+ }
+ getDeviceOwnerAdminLocked().isNetworkLoggingEnabled = enabled;
+ saveSettingsLocked(mInjector.userHandleGetCallingUserId());
+
+ setNetworkLoggingActiveInternal(enabled);
+ }
+
+ private synchronized void setNetworkLoggingActiveInternal(boolean active) {
+ final long callingIdentity = mInjector.binderClearCallingIdentity();
+ try {
+ if (active) {
+ mNetworkLogger = new NetworkLogger(this, mInjector.getPackageManagerInternal());
+ if (!mNetworkLogger.startNetworkLogging()) {
+ mNetworkLogger = null;
+ Slog.wtf(LOG_TAG, "Network logging could not be started due to the logging"
+ + " service not being available yet.");
+ }
+ } else {
+ if (mNetworkLogger != null && !mNetworkLogger.stopNetworkLogging()) {
+ mNetworkLogger = null;
+ Slog.wtf(LOG_TAG, "Network logging could not be stopped due to the logging"
+ + " service not being available yet.");
+ }
+ mNetworkLogger = null;
+ }
+ } finally {
+ mInjector.binderRestoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
+ public boolean isNetworkLoggingEnabled(ComponentName admin) {
+ if (!mHasFeature) {
+ return false;
+ }
+ Preconditions.checkNotNull(admin);
+ synchronized (this) {
+ getActiveAdminForCallerLocked(admin, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
+ return isNetworkLoggingEnabledInternalLocked();
+ }
+ }
+
+ private boolean isNetworkLoggingEnabledInternalLocked() {
+ ActiveAdmin deviceOwner = getDeviceOwnerAdminLocked();
+ return (deviceOwner != null) && deviceOwner.isNetworkLoggingEnabled;
+ }
+
+ /*
+ * A maximum of 1200 events are returned, and the total marshalled size is in the order of
+ * 100kB, so returning a List instead of ParceledListSlice is acceptable.
+ * Ideally this would be done with ParceledList, however it only supports homogeneous types.
+ *
+ * @see NetworkLoggingHandler#MAX_EVENTS_PER_BATCH
+ */
+ @Override
+ public synchronized List<NetworkEvent> retrieveNetworkLogs(ComponentName admin,
+ long batchToken) {
+ if (!mHasFeature) {
+ return null;
+ }
+ Preconditions.checkNotNull(admin);
+ ensureDeviceOwnerManagingSingleUser(admin);
+
+ if (mNetworkLogger == null) {
+ return null;
+ }
+ return isNetworkLoggingEnabledInternalLocked()
+ ? mNetworkLogger.retrieveLogs(batchToken)
+ : null;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
new file mode 100644
index 000000000000..8cb13da07c16
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.server.devicepolicy;
+
+import android.app.admin.ConnectEvent;
+import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.content.pm.PackageManagerInternal;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.ServiceThread;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class for managing network logging.
+ * This class is not thread-safe, callers should synchronize access.
+ */
+final class NetworkLogger {
+
+ private static final String TAG = NetworkLogger.class.getSimpleName();
+
+ private final DevicePolicyManagerService mDpm;
+ private final PackageManagerInternal mPm;
+
+ private IIpConnectivityMetrics mIpConnectivityMetrics;
+ private ServiceThread mHandlerThread;
+ private NetworkLoggingHandler mNetworkLoggingHandler;
+ private boolean mIsLoggingEnabled;
+
+ private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() {
+ @Override
+ public void onDnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount,
+ long timestamp, int uid) {
+ if (!mIsLoggingEnabled) {
+ return;
+ }
+ DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount,
+ mPm.getNameForUid(uid), timestamp);
+ sendNetworkEvent(dnsEvent);
+ }
+
+ @Override
+ public void onConnectEvent(String ipAddr, int port, long timestamp, int uid) {
+ if (!mIsLoggingEnabled) {
+ return;
+ }
+ ConnectEvent connectEvent = new ConnectEvent(ipAddr, port, mPm.getNameForUid(uid),
+ timestamp);
+ sendNetworkEvent(connectEvent);
+ }
+
+ private void sendNetworkEvent(NetworkEvent event) {
+ Message msg = mNetworkLoggingHandler.obtainMessage(
+ NetworkLoggingHandler.LOG_NETWORK_EVENT_MSG);
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(NetworkLoggingHandler.NETWORK_EVENT_KEY, event);
+ msg.setData(bundle);
+ mNetworkLoggingHandler.sendMessage(msg);
+ }
+ };
+
+ NetworkLogger(DevicePolicyManagerService dpm, PackageManagerInternal pm) {
+ mDpm = dpm;
+ mPm = pm;
+ }
+
+ private boolean checkIpConnectivityMetricsService() {
+ if (mIpConnectivityMetrics != null) {
+ return true;
+ }
+ final IIpConnectivityMetrics service = mDpm.mInjector.getIIpConnectivityMetrics();
+ if (service == null) {
+ return false;
+ }
+ mIpConnectivityMetrics = service;
+ return true;
+ }
+
+ boolean startNetworkLogging() {
+ Log.d(TAG, "Starting network logging.");
+ if (!checkIpConnectivityMetricsService()) {
+ // the IIpConnectivityMetrics service should have been present at this point
+ Slog.wtf(TAG, "Failed to register callback with IIpConnectivityMetrics.");
+ return false;
+ }
+ try {
+ if (mIpConnectivityMetrics.registerNetdEventCallback(mNetdEventCallback)) {
+ mHandlerThread = new ServiceThread(TAG, Process.THREAD_PRIORITY_BACKGROUND,
+ /* allowIo */ false);
+ mHandlerThread.start();
+ mNetworkLoggingHandler = new NetworkLoggingHandler(mHandlerThread.getLooper(),
+ mDpm);
+ mNetworkLoggingHandler.scheduleBatchFinalization(
+ NetworkLoggingHandler.BATCH_FINALIZATION_TIMEOUT_MS);
+ mIsLoggingEnabled = true;
+ return true;
+ } else {
+ return false;
+ }
+ } catch (RemoteException re) {
+ Slog.wtf(TAG, "Failed to make remote calls to register the callback", re);
+ return false;
+ }
+ }
+
+ boolean stopNetworkLogging() {
+ Log.d(TAG, "Stopping network logging");
+ // stop the logging regardless of whether we fail to unregister listener
+ mIsLoggingEnabled = false;
+ try {
+ if (!checkIpConnectivityMetricsService()) {
+ // the IIpConnectivityMetrics service should have been present at this point
+ Slog.wtf(TAG, "Failed to unregister callback with IIpConnectivityMetrics.");
+ // logging is forcefully disabled even if unregistering fails
+ return true;
+ }
+ return mIpConnectivityMetrics.unregisterNetdEventCallback();
+ } catch (RemoteException re) {
+ Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re);
+ } finally {
+ mHandlerThread.quitSafely();
+ return true;
+ }
+ }
+
+ List<NetworkEvent> retrieveLogs(long batchToken) {
+ return mNetworkLoggingHandler.retrieveFullLogBatch(batchToken);
+ }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
new file mode 100644
index 000000000000..957d4c54c2f9
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -0,0 +1,124 @@
+/*
+ * 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 com.android.server.devicepolicy;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.ConnectEvent;
+import android.app.admin.DnsEvent;
+import android.app.admin.NetworkEvent;
+import android.net.IIpConnectivityMetrics;
+import android.net.INetdEventCallback;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A Handler class for managing network logging on a background thread.
+ */
+final class NetworkLoggingHandler extends Handler {
+
+ private static final String TAG = NetworkLoggingHandler.class.getSimpleName();
+
+ static final String NETWORK_EVENT_KEY = "network_event";
+
+ // est. ~128kB of memory usage per full batch TODO(mkarpinski): fine tune based on testing data
+ // If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc
+ private static final int MAX_EVENTS_PER_BATCH = 1200;
+ static final long BATCH_FINALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(90);
+
+ static final int LOG_NETWORK_EVENT_MSG = 1;
+ static final int FINALIZE_BATCH_MSG = 2;
+
+ private final DevicePolicyManagerService mDpm;
+
+ // threadsafe as it's Handler's thread confined
+ @GuardedBy("this")
+ private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>();
+
+ @GuardedBy("this")
+ private ArrayList<NetworkEvent> mFullBatch;
+
+ // each full batch is represented by its token, which the DPC has to provide back to revieve it
+ @GuardedBy("this")
+ private long mCurrentFullBatchToken;
+
+ NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
+ super(looper);
+ mDpm = dpm;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case LOG_NETWORK_EVENT_MSG: {
+ NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY);
+ if (networkEvent != null) {
+ mNetworkEvents.add(networkEvent);
+ if (mNetworkEvents.size() >= MAX_EVENTS_PER_BATCH) {
+ finalizeBatchAndNotifyDeviceOwner();
+ }
+ }
+ break;
+ }
+ case FINALIZE_BATCH_MSG: {
+ finalizeBatchAndNotifyDeviceOwner();
+ break;
+ }
+ }
+ }
+
+ void scheduleBatchFinalization(long delay) {
+ removeMessages(FINALIZE_BATCH_MSG);
+ sendMessageDelayed(obtainMessage(FINALIZE_BATCH_MSG), delay);
+ }
+
+ private synchronized void finalizeBatchAndNotifyDeviceOwner() {
+ mFullBatch = mNetworkEvents;
+ // start a new batch from scratch
+ mNetworkEvents = new ArrayList<NetworkEvent>();
+ scheduleBatchFinalization(BATCH_FINALIZATION_TIMEOUT_MS);
+ // notify DO that there's a new non-empty batch waiting
+ if (mFullBatch.size() > 0) {
+ mCurrentFullBatchToken++;
+ Bundle extras = new Bundle();
+ extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
+ extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
+ Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
+ + mCurrentFullBatchToken);
+ mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
+ } else {
+ mFullBatch = null;
+ }
+ }
+
+ synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) {
+ if (batchToken != mCurrentFullBatchToken) {
+ return null;
+ }
+ return mFullBatch;
+ }
+}
+
diff --git a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java b/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
index 9e2fd6231ba9..af4a374bffdd 100644
--- a/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/connectivity/NetdEventListenerServiceTest.java
@@ -22,6 +22,7 @@ import android.net.Network;
import android.net.metrics.DnsEvent;
import android.net.metrics.INetdEventListener;
import android.net.metrics.IpConnectivityLog;
+import android.os.RemoteException;
import junit.framework.TestCase;
import org.junit.Before;
@@ -157,9 +158,13 @@ public class NetdEventListenerServiceTest extends TestCase {
}
void log(int netId, int[] latencies) {
- for (int l : latencies) {
- mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null, 0,
- 0);
+ try {
+ for (int l : latencies) {
+ mNetdEventListenerService.onDnsEvent(netId, EVENT_TYPE, RETURN_CODE, l, null, null,
+ 0, 0);
+ }
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
new file mode 100644
index 000000000000..315d37cbd662
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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 com.android.server.devicepolicy;
+
+import android.app.admin.ConnectEvent;
+import android.app.admin.DnsEvent;
+import android.os.Parcel;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static junit.framework.Assert.assertEquals;
+
+@SmallTest
+public class NetworkEventTest extends DpmTestBase {
+
+ /**
+ * Test parceling and unparceling of a ConnectEvent.
+ */
+ public void testConnectEventParceling() {
+ ConnectEvent event = new ConnectEvent("127.0.0.1", 80, "com.android.whateverdude", 100000);
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(event, 0);
+ p.setDataPosition(0);
+ ConnectEvent unparceledEvent = p.readParcelable(NetworkEventTest.class.getClassLoader());
+ p.recycle();
+ assertEquals(event.getIpAddress(), unparceledEvent.getIpAddress());
+ assertEquals(event.getPort(), unparceledEvent.getPort());
+ assertEquals(event.getPackageName(), unparceledEvent.getPackageName());
+ assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp());
+ }
+
+ /**
+ * Test parceling and unparceling of a DnsEvent.
+ */
+ public void testDnsEventParceling() {
+ DnsEvent event = new DnsEvent("d.android.com", new String[]{"192.168.0.1", "127.0.0.1"}, 2,
+ "com.android.whateverdude", 100000);
+ Parcel p = Parcel.obtain();
+ p.writeParcelable(event, 0);
+ p.setDataPosition(0);
+ DnsEvent unparceledEvent = p.readParcelable(NetworkEventTest.class.getClassLoader());
+ p.recycle();
+ assertEquals(event.getHostname(), unparceledEvent.getHostname());
+ assertEquals(event.getIpAddresses()[0], unparceledEvent.getIpAddresses()[0]);
+ assertEquals(event.getIpAddresses()[1], unparceledEvent.getIpAddresses()[1]);
+ assertEquals(event.getIpAddressesCount(), unparceledEvent.getIpAddressesCount());
+ assertEquals(event.getPackageName(), unparceledEvent.getPackageName());
+ assertEquals(event.getTimestamp(), unparceledEvent.getTimestamp());
+ }
+}