diff options
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()); + } +} |