diff options
| author | 2016-10-12 16:40:06 +0100 | |
|---|---|---|
| committer | 2016-11-02 09:00:21 +0000 | |
| commit | f77ee4f1b79929a77f603e5e879f3616ae464e3e (patch) | |
| tree | 80fdafe4527d095043d6e0f62064bcdade34be66 | |
| parent | dd9bb4fdd9f4b528734a7907d2bc92841ca648ab (diff) | |
[DPM] Management and retrieval of network logs
This CL follows up on ag/1530343 and adds:
1) Various network events.
2) Retrieval method in DPM and APIs in DeviceAdminReceiver.
3) Extension of NetworkLogger and it's NetworkLoggingHandler.
Test: runtest --path frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java
Bug: 29748723
Change-Id: I42a1a477e7c75c109a3982f809c22732b814e8b2
17 files changed, 655 insertions, 5 deletions
diff --git a/Android.mk b/Android.mk index 09bdea233d6f..862569da87fd 100644 --- a/Android.mk +++ b/Android.mk @@ -585,6 +585,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/app/admin/PasswordMetrics.aidl \ frameworks/base/core/java/android/print/PrintDocumentInfo.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..e05feafcc93c --- /dev/null +++ b/core/java/android/app/admin/ConnectEvent.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class that represents a connect library call event. + * @hide + */ +public final class ConnectEvent extends NetworkEvent implements Parcelable { + + /** The destination IP address. */ + private final String ipAddress; + + /** The destination port number. */ + private final int port; + + public ConnectEvent(String ipAddress, int port, String packageName, long timestamp) { + super(packageName, timestamp); + this.ipAddress = ipAddress; + this.port = port; + } + + private ConnectEvent(Parcel in) { + this.ipAddress = in.readString(); + this.port = in.readInt(); + this.packageName = in.readString(); + this.timestamp = in.readLong(); + } + + public String getIpAddress() { + return ipAddress; + } + + public int getPort() { + return port; + } + + @Override + public String toString() { + return String.format("ConnectEvent(%s, %d, %d, %s)", ipAddress, port, timestamp, + packageName); + } + + public static final Parcelable.Creator<ConnectEvent> CREATOR + = new Parcelable.Creator<ConnectEvent>() { + @Override + public ConnectEvent createFromParcel(Parcel in) { + if (in.readInt() != PARCEL_TOKEN_CONNECT_EVENT) { + return null; + } + return new ConnectEvent(in); + } + + @Override + public ConnectEvent[] newArray(int size) { + return new ConnectEvent[size]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + // write parcel token first + out.writeInt(PARCEL_TOKEN_CONNECT_EVENT); + out.writeString(ipAddress); + out.writeInt(port); + out.writeString(packageName); + out.writeLong(timestamp); + } +} + diff --git a/core/java/android/app/admin/DeviceAdminReceiver.java b/core/java/android/app/admin/DeviceAdminReceiver.java index dd70b5dfd1f0..360087c4dfe1 100644 --- a/core/java/android/app/admin/DeviceAdminReceiver.java +++ b/core/java/android/app/admin/DeviceAdminReceiver.java @@ -276,6 +276,15 @@ 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 string containing the SHA-256 hash of the bugreport file. * * @see #ACTION_BUGREPORT_SHARE @@ -635,6 +644,22 @@ public class DeviceAdminReceiver extends BroadcastReceiver { } /** + * Called when a new batch of network logs can be retrieved. This callback method will only ever + * be called when network logging is enabled. The logs can only be retrieved while network + * logging is enabled. + * + * <p>This callback is only applicable to device owners. + * + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + * @see DevicePolicyManager#retrieveNetworkLogs(ComponentName) + * + * @hide + */ + public void onNetworkLogsAvailable(Context context, Intent intent) { + } + + /** * Intercept standard device administrator broadcasts. Implementations * should not override this method; it is better to implement the * convenience callbacks for each action. @@ -688,6 +713,8 @@ 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)) { + onNetworkLogsAvailable(context, intent); } } } diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 138ec0255571..94cfacaad322 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -26,6 +26,7 @@ import android.annotation.SystemApi; import android.annotation.UserIdInt; import android.annotation.WorkerThread; import android.app.Activity; +import android.app.admin.NetworkEvent; import android.app.admin.PasswordMetrics; import android.app.admin.SecurityLog.SecurityEvent; import android.content.ComponentName; @@ -6648,6 +6649,7 @@ public class DevicePolicyManager { * @throws {@link SecurityException} if {@code admin} is not a device owner. * @throws {@link RemoteException} if network logging could not be enabled or disabled due to * the logging service not being available + * @see #retrieveNetworkLogs * * @hide */ @@ -6677,4 +6679,31 @@ public class DevicePolicyManager { throw re.rethrowFromSystemServer(); } } + + /** + * Called by device owner to retrieve a new batch of network logging events. + * + * <p> {@link NetworkEvent} can be one of {@link DnsEvent} or {@link ConnectEvent}. + * + * <p> The list of network events is sorted chronologically, and contains at most 1200 events. + * + * <p> Access to the logs is rate limited and this method will only return a new batch of logs + * after the device device owner has been notified via + * {@link DeviceAdminReceiver#onNetworkLogsAvailable}. + * + * @param admin Which {@link DeviceAdminReceiver} this request is associated with. + * @return A new batch of network logs which is a list of {@link NetworkEvent}. Returns + * {@code null} if there's no batch currently awaiting for retrieval or if logging is disabled. + * @throws {@link SecurityException} if {@code admin} is not a device owner. + * + * @hide + */ + public List<NetworkEvent> retrieveNetworkLogs(@NonNull ComponentName admin) { + throwIfParentInstance("retrieveNetworkLogs"); + try { + return mService.retrieveNetworkLogs(admin); + } catch (RemoteException re) { + throw re.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/app/admin/DnsEvent.aidl b/core/java/android/app/admin/DnsEvent.aidl new file mode 100644 index 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..0ec134acdad5 --- /dev/null +++ b/core/java/android/app/admin/DnsEvent.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * A class that represents a DNS lookup event. + * @hide + */ +public final class DnsEvent extends NetworkEvent implements Parcelable { + + /** The hostname that was looked up. */ + private final String hostname; + + /** Contains (possibly a subset of) the IP addresses returned. */ + private final String[] ipAddresses; + + /** + * The number of IP addresses returned from the DNS lookup event. May be different from the + * length of ipAddresses if there were too many addresses to log. + */ + private final int ipAddressesCount; + + public DnsEvent(String hostname, String[] ipAddresses, int ipAddressesCount, + String packageName, long timestamp) { + super(packageName, timestamp); + this.hostname = hostname; + this.ipAddresses = ipAddresses; + this.ipAddressesCount = ipAddressesCount; + } + + private DnsEvent(Parcel in) { + this.hostname = in.readString(); + this.ipAddresses = in.createStringArray(); + this.ipAddressesCount = in.readInt(); + this.packageName = in.readString(); + this.timestamp = in.readLong(); + } + + /** Returns the hostname that was looked up. */ + public String getHostname() { + return hostname; + } + + /** Returns (possibly a subset of) the IP addresses returned. */ + public String[] getIpAddresses() { + return ipAddresses; + } + + /** + * Returns the number of IP addresses returned from the DNS lookup event. May be different from + * the length of ipAddresses if there were too many addresses to log. + */ + public int getIpAddressesCount() { + return ipAddressesCount; + } + + @Override + public String toString() { + return String.format("DnsEvent(%s, %s, %d, %d, %s)", hostname, + (ipAddresses == null) ? "NONE" : String.join(" ", ipAddresses), + ipAddressesCount, timestamp, packageName); + } + + public static final Parcelable.Creator<DnsEvent> CREATOR + = new Parcelable.Creator<DnsEvent>() { + @Override + public DnsEvent createFromParcel(Parcel in) { + if (in.readInt() != PARCEL_TOKEN_DNS_EVENT) { + return null; + } + return new DnsEvent(in); + } + + @Override + public DnsEvent[] newArray(int size) { + return new DnsEvent[size]; + } + }; + + @Override + public void writeToParcel(Parcel out, int flags) { + // write parcel token first + out.writeInt(PARCEL_TOKEN_DNS_EVENT); + out.writeString(hostname); + out.writeStringArray(ipAddresses); + out.writeInt(ipAddressesCount); + out.writeString(packageName); + out.writeLong(timestamp); + } +} + diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 3cfa1e807b96..b0aec8cd2571 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -17,6 +17,7 @@ package android.app.admin; +import android.app.admin.NetworkEvent; import android.app.admin.SystemUpdatePolicy; import android.app.admin.PasswordMetrics; import android.content.ComponentName; @@ -317,4 +318,5 @@ interface IDevicePolicyManager { void setNetworkLoggingEnabled(in ComponentName admin, boolean enabled); boolean isNetworkLoggingEnabled(in ComponentName admin); + List<NetworkEvent> retrieveNetworkLogs(in ComponentName admin); } diff --git a/core/java/android/app/admin/NetworkEvent.aidl b/core/java/android/app/admin/NetworkEvent.aidl new file mode 100644 index 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..ec7ed00debb4 --- /dev/null +++ b/core/java/android/app/admin/NetworkEvent.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.app.admin; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.ParcelFormatException; + +/** + * An abstract class that represents a network event. + * @hide + */ +public abstract class NetworkEvent implements Parcelable { + + protected static final int PARCEL_TOKEN_DNS_EVENT = 1; + protected static final int PARCEL_TOKEN_CONNECT_EVENT = 2; + + /** The package name of the UID that performed the query. */ + protected String packageName; + + /** The timestamp of the event being reported in milliseconds. */ + protected long timestamp; + + protected NetworkEvent() { + //empty constructor + } + + protected NetworkEvent(String packageName, long timestamp) { + this.packageName = packageName; + this.timestamp = timestamp; + } + + /** Returns the package name of the UID that performed the query. */ + public String getPackageName() { + return packageName; + } + + /** Returns the timestamp of the event being reported in milliseconds. */ + public long getTimestamp() { + return timestamp; + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<NetworkEvent> CREATOR + = new Parcelable.Creator<NetworkEvent>() { + public NetworkEvent createFromParcel(Parcel in) { + final int initialPosition = in.dataPosition(); + final int parcelToken = in.readInt(); + // we need to move back to the position from before we read parcelToken + in.setDataPosition(initialPosition); + switch (parcelToken) { + case PARCEL_TOKEN_DNS_EVENT: + return DnsEvent.CREATOR.createFromParcel(in); + case PARCEL_TOKEN_CONNECT_EVENT: + return ConnectEvent.CREATOR.createFromParcel(in); + default: + throw new ParcelFormatException("Unexpected NetworkEvent token in parcel: " + + parcelToken); + } + } + + public NetworkEvent[] newArray(int size) { + return new NetworkEvent[size]; + } + }; +} + diff --git a/core/java/android/content/pm/PackageManagerInternal.java b/core/java/android/content/pm/PackageManagerInternal.java index da4eb2d288cf..1f013ae02a87 100644 --- a/core/java/android/content/pm/PackageManagerInternal.java +++ b/core/java/android/content/pm/PackageManagerInternal.java @@ -189,4 +189,17 @@ public abstract class PackageManagerInternal { public abstract void revokeRuntimePermission(String packageName, String name, int userId, boolean overridePolicy); + /** + * Retrieve the official name associated with a user id. This name is + * guaranteed to never change, though it is possible for the underlying + * user id to be changed. That is, if you are storing information about + * user ids in persistent storage, you should use the string returned + * by this function instead of the raw user-id. + * + * @param uid The user id for which you would like to retrieve a name. + * @return Returns a unique name for the given user id, or null if the + * user id is not currently assigned. + */ + public abstract String getNameForUid(int uid); + } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b38bb1ec779f..7f657eaa49f3 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -454,6 +454,7 @@ <protected-broadcast android:name="com.android.server.Wifi.action.TOGGLE_PNO" /> <protected-broadcast android:name="intent.action.ACTION_RF_BAND_INFO" /> <protected-broadcast android:name="android.intent.action.MEDIA_RESOURCE_GRANTED" /> + <protected-broadcast android:name="android.app.action.NETWORK_LOGS_AVAILABLE" /> <protected-broadcast android:name="android.app.action.SECURITY_LOGS_AVAILABLE" /> <protected-broadcast android:name="android.app.action.INTERRUPTION_FILTER_CHANGED" /> diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 59c4fb9e7591..3705814badfe 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -21228,6 +21228,11 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName()); PackageManagerService.this.revokeRuntimePermission(packageName, name, userId, overridePolicy); } + + @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 42b5dc0f0bd0..6006c6bf6d3b 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -51,6 +51,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.PasswordMetrics; import android.app.admin.SecurityLog; import android.app.admin.SecurityLog.SecurityEvent; @@ -9558,4 +9559,23 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { 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. + */ + @Override + public synchronized List<NetworkEvent> retrieveNetworkLogs(ComponentName admin) { + if (!mHasFeature) { + return null; + } + Preconditions.checkNotNull(admin); + ensureDeviceOwnerManagingSingleUser(admin); + + if (mNetworkLogger == null) { + return null; + } + return isNetworkLoggingEnabledInternal() ? mNetworkLogger.retrieveLogs() : null; + } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java index db17ca23aa5f..185ccc232aa9 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLogger.java @@ -16,9 +16,16 @@ 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; @@ -40,6 +47,8 @@ final class NetworkLogger { private final PackageManagerInternal mPm; private IIpConnectivityMetrics mIpConnectivityMetrics; + private ServiceThread mHandlerThread; + private NetworkLoggingHandler mNetworkLoggingHandler; private boolean mIsLoggingEnabled; private final INetdEventCallback mNetdEventCallback = new INetdEventCallback.Stub() { @@ -49,7 +58,9 @@ final class NetworkLogger { if (!mIsLoggingEnabled) { return; } - // TODO(mkarpinski): send msg with data to Handler + DnsEvent dnsEvent = new DnsEvent(hostname, ipAddresses, ipAddressesCount, + mPm.getNameForUid(uid), timestamp); + sendNetworkEvent(dnsEvent); } @Override @@ -57,7 +68,18 @@ final class NetworkLogger { if (!mIsLoggingEnabled) { return; } - // TODO(mkarpinski): send msg with data to Handler + 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); } }; @@ -87,7 +109,13 @@ final class NetworkLogger { } try { if (mIpConnectivityMetrics.registerNetdEventCallback(mNetdEventCallback)) { - // TODO(mkarpinski): start a new ServiceThread, instantiate a Handler etc. + 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 { @@ -101,7 +129,7 @@ final class NetworkLogger { boolean stopNetworkLogging() { Log.d(TAG, "Stopping network logging"); - // stop the logging regardless of whether we failed to unregister listener + // stop the logging regardless of whether we fail to unregister listener mIsLoggingEnabled = false; try { if (!checkIpConnectivityMetricsService()) { @@ -114,8 +142,12 @@ final class NetworkLogger { } catch (RemoteException re) { Slog.wtf(TAG, "Failed to make remote calls to unregister the callback", re); } finally { - // TODO(mkarpinski): quitSafely() the Handler + mHandlerThread.quitSafely(); return true; } } + + List<NetworkEvent> retrieveLogs() { + return mNetworkLoggingHandler.retrieveFullLogBatch(); + } } 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..96884e6db711 --- /dev/null +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -0,0 +1,113 @@ +/* + * 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 + private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>(); + + @GuardedBy("this") + private ArrayList<NetworkEvent> mFullBatch; + + 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) { + mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, + /* extras */ null); + } else { + mFullBatch = null; + } + } + + synchronized List<NetworkEvent> retrieveFullLogBatch() { + List<NetworkEvent> ret = mFullBatch; + mFullBatch = null; + return ret; + } +} + 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()); + } +} |