diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java index 4a6bee5c7365e..f91f959d99389 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java @@ -29,10 +29,10 @@ import android.util.LongSparseArray; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; /** * A Handler class for managing network logging on a background thread. @@ -81,6 +81,7 @@ final class NetworkLoggingHandler extends Handler { } }; + @VisibleForTesting static final int LOG_NETWORK_EVENT_MSG = 1; /** Network events accumulated so far to be finalized into a batch at some point. */ @@ -106,9 +107,15 @@ final class NetworkLoggingHandler extends Handler { private long mLastRetrievedBatchToken; NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { + this(looper, dpm, 0 /* event id */); + } + + @VisibleForTesting + NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm, long id) { super(looper); - mDpm = dpm; - mAlarmManager = mDpm.mInjector.getAlarmManager(); + this.mDpm = dpm; + this.mAlarmManager = mDpm.mInjector.getAlarmManager(); + this.mId = id; } @Override @@ -189,7 +196,13 @@ final class NetworkLoggingHandler extends Handler { if (mNetworkEvents.size() > 0) { // Assign ids to the events. for (NetworkEvent event : mNetworkEvents) { - event.setId(mId++); + event.setId(mId); + if (mId == Long.MAX_VALUE) { + Slog.i(TAG, "Reached maximum id value; wrapping around ." + mCurrentBatchToken); + mId = 0; + } else { + mId++; + } } // Finalize the batch and start a new one from scratch. if (mBatches.size() >= MAX_BATCHES) { diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java index a92d1db2616b1..c698312eedec5 100644 --- a/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java +++ b/services/tests/servicestests/src/com/android/server/devicepolicy/NetworkEventTest.java @@ -15,13 +15,131 @@ */ package com.android.server.devicepolicy; +import static com.android.server.devicepolicy.NetworkLoggingHandler.LOG_NETWORK_EVENT_MSG; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + import android.app.admin.ConnectEvent; +import android.app.admin.DeviceAdminReceiver; +import android.app.admin.DevicePolicyManagerInternal; import android.app.admin.DnsEvent; +import android.app.admin.NetworkEvent; +import android.content.Intent; +import android.os.Bundle; +import android.os.Message; import android.os.Parcel; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.test.TestLooper; import android.test.suitebuilder.annotation.SmallTest; +import com.android.server.LocalServices; +import com.android.server.SystemService; + +import org.mockito.ArgumentCaptor; + +import java.util.List; + @SmallTest public class NetworkEventTest extends DpmTestBase { + private static final int MAX_EVENTS_PER_BATCH = 1200; + + private DpmMockContext mSpiedDpmMockContext; + private DevicePolicyManagerServiceTestable mDpmTestable; + + @Override + protected void setUp() throws Exception { + super.setUp(); + mSpiedDpmMockContext = spy(mMockContext); + mSpiedDpmMockContext.callerPermissions.add( + android.Manifest.permission.MANAGE_DEVICE_ADMINS); + doNothing().when(mSpiedDpmMockContext).sendBroadcastAsUser(any(Intent.class), + any(UserHandle.class)); + LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class); + mDpmTestable = new DevicePolicyManagerServiceTestable(getServices(), mSpiedDpmMockContext); + setUpPackageManagerForAdmin(admin1, DpmMockContext.CALLER_UID); + mDpmTestable.setActiveAdmin(admin1, true, DpmMockContext.CALLER_USER_HANDLE); + } + + public void testNetworkEventId_monotonicallyIncreasing() throws Exception { + // GIVEN the handler has not processed any events. + long startingId = 0; + + // WHEN the handler has processed the events. + List events = fillHandlerWithFullBatchOfEvents(startingId); + + // THEN the events are in a batch. + assertTrue("Batch not at the returned token.", + events != null && events.size() == MAX_EVENTS_PER_BATCH); + // THEN event ids are monotonically increasing. + long expectedId = startingId; + for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) { + assertEquals("At index " + i + ", the event has the wrong id.", expectedId, + events.get(i).getId()); + expectedId++; + } + } + + public void testNetworkEventId_wrapsAround() throws Exception { + // GIVEN the handler has almost processed Long.MAX_VALUE events. + int gap = 5; + long startingId = Long.MAX_VALUE - gap; + + // WHEN the handler has processed the events. + List events = fillHandlerWithFullBatchOfEvents(startingId); + + // THEN the events are in a batch. + assertTrue("Batch not at the returned token.", + events != null && events.size() == MAX_EVENTS_PER_BATCH); + // THEN event ids are monotonically increasing. + long expectedId = startingId; + for (int i = 0; i < gap; i++) { + assertEquals("At index " + i + ", the event has the wrong id.", expectedId, + events.get(i).getId()); + expectedId++; + } + // THEN event ids are reset when the id reaches the maximum possible value. + assertEquals("Event was not assigned the maximum id value.", Long.MAX_VALUE, + events.get(gap).getId()); + assertEquals("Event id was not reset.", 0, events.get(gap + 1).getId()); + // THEN event ids are monotonically increasing. + expectedId = 0; + for (int i = gap + 1; i < MAX_EVENTS_PER_BATCH; i++) { + assertEquals("At index " + i + ", the event has the wrong id.", expectedId, + events.get(i).getId()); + expectedId++; + } + } + + private List fillHandlerWithFullBatchOfEvents(long startingId) throws Exception { + // GIVEN a handler with events + NetworkLoggingHandler handler = new NetworkLoggingHandler(new TestLooper().getLooper(), + mDpmTestable, startingId); + // GIVEN network events are sent to the handler. + for (int i = 0; i < MAX_EVENTS_PER_BATCH; i++) { + ConnectEvent event = new ConnectEvent("some_ip_address", 800, "com.google.foo", + SystemClock.currentThreadTimeMillis()); + Message msg = new Message(); + msg.what = LOG_NETWORK_EVENT_MSG; + Bundle bundle = new Bundle(); + bundle.putParcelable(NetworkLoggingHandler.NETWORK_EVENT_KEY, event); + msg.setData(bundle); + handler.handleMessage(msg); + } + + // WHEN the handler processes the events. + ArgumentCaptor intentCaptor = ArgumentCaptor.forClass(Intent.class); + verify(mSpiedDpmMockContext).sendBroadcastAsUser(intentCaptor.capture(), + any(UserHandle.class)); + assertEquals(intentCaptor.getValue().getAction(), + DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE); + long token = intentCaptor.getValue().getExtras().getLong( + DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, 0); + return handler.retrieveFullLogBatch(token); + } /** * Test parceling and unparceling of a ConnectEvent.