Loading services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +68 −33 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.util.Log; import android.util.LongSparseArray; import com.android.internal.annotations.GuardedBy; Loading @@ -44,12 +45,21 @@ final class NetworkLoggingHandler extends Handler { // If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc private static final int MAX_EVENTS_PER_BATCH = 1200; private static final long BATCH_FINALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(90); private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30); /** * Maximum number of batches to store in memory. If more batches are generated and the DO * doesn't fetch them, we will discard the oldest one. */ private static final int MAX_BATCHES = 5; private static final long BATCH_FINALIZATION_TIMEOUT_MS = 90 * 60 * 1000; // 1.5h private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = 30 * 60 * 1000; // 30m private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout"; /** Delay after which older batches get discarded after a retrieval. */ private static final long RETRIEVED_BATCH_DISCARD_DELAY_MS = 5 * 60 * 1000; // 5m private final DevicePolicyManagerService mDpm; private final AlarmManager mAlarmManager; Loading @@ -66,22 +76,27 @@ final class NetworkLoggingHandler extends Handler { static final int LOG_NETWORK_EVENT_MSG = 1; // threadsafe as it's Handler's thread confined /** Network events accumulated so far to be finalized into a batch at some point. */ @GuardedBy("this") private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>(); private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>(); /** * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}. */ @GuardedBy("this") private ArrayList<NetworkEvent> mFullBatch; private final LongSparseArray<ArrayList<NetworkEvent>> mBatches = new LongSparseArray<>(MAX_BATCHES); @GuardedBy("this") private boolean mPaused = false; // each full batch is represented by its token, which the DPC has to provide back to retrieve it @GuardedBy("this") private long mCurrentFullBatchToken; private long mCurrentBatchToken; @GuardedBy("this") private long mLastRetrievedFullBatchToken; private long mLastRetrievedBatchToken; NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { super(looper); Loading @@ -93,7 +108,7 @@ final class NetworkLoggingHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case LOG_NETWORK_EVENT_MSG: { NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY); final NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY); if (networkEvent != null) { synchronized (NetworkLoggingHandler.this) { mNetworkEvents.add(networkEvent); Loading @@ -113,6 +128,8 @@ final class NetworkLoggingHandler extends Handler { void scheduleBatchFinalization() { final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS; // We use alarm manager and not just postDelayed here to ensure the batch gets finalized // even if the device goes to sleep. mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG, mBatchTimeoutAlarmListener, this); Loading @@ -131,62 +148,80 @@ final class NetworkLoggingHandler extends Handler { return; } Log.d(TAG, "Resumed network logging. Current batch=" + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken); Log.d(TAG, "Resumed network logging. Current batch=" + mCurrentBatchToken + ", LastRetrievedBatch=" + mLastRetrievedBatchToken); mPaused = false; // If there is a full batch ready that the device owner hasn't been notified about, do it // now. if (mFullBatch != null && mFullBatch.size() > 0 && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) { // If there is a batch ready that the device owner hasn't been notified about, do it now. if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) { scheduleBatchFinalization(); notifyDeviceOwnerLocked(); } } synchronized void discardLogs() { mFullBatch = null; mNetworkEvents = new ArrayList<NetworkEvent>(); mBatches.clear(); mNetworkEvents = new ArrayList<>(); Log.d(TAG, "Discarded all network logs"); } @GuardedBy("this") private void finalizeBatchAndNotifyDeviceOwnerLocked() { if (mNetworkEvents.size() > 0) { // finalize the batch and start a new one from scratch mFullBatch = mNetworkEvents; mCurrentFullBatchToken++; mNetworkEvents = new ArrayList<NetworkEvent>(); // Finalize the batch and start a new one from scratch. if (mBatches.size() >= MAX_BATCHES) { // Remove the oldest batch if we hit the limit. mBatches.removeAt(0); } mCurrentBatchToken++; mBatches.append(mCurrentBatchToken, mNetworkEvents); mNetworkEvents = new ArrayList<>(); if (!mPaused) { notifyDeviceOwnerLocked(); } } else { // don't notify the DO, since there are no events; DPC can still retrieve // Don't notify the DO, since there are no events; DPC can still retrieve // the last full batch if not paused. Log.d(TAG, "Was about to finalize the batch, but there were no events to send to" + " the DPC, the batchToken of last available batch: " + mCurrentFullBatchToken); + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken); } // regardless of whether the batch was non-empty schedule a new finalization after timeout // Regardless of whether the batch was non-empty schedule a new finalization after timeout. scheduleBatchFinalization(); } /** Sends a notification to the DO. Should only be called when there is a batch available. */ @GuardedBy("this") private void notifyDeviceOwnerLocked() { Bundle extras = new Bundle(); extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken); extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size()); final Bundle extras = new Bundle(); final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size(); extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken); extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, lastBatchSize); Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " + mCurrentFullBatchToken); + mCurrentBatchToken); mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); } synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) { if (batchToken != mCurrentFullBatchToken) { synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) { final int index = mBatches.indexOfKey(batchToken); if (index < 0) { // Invalid token or batch has already been discarded. return null; } mLastRetrievedFullBatchToken = mCurrentFullBatchToken; return mFullBatch; // Schedule this and older batches to be discarded after a delay to lessen memory load // without interfering with the admin's ability to collect logs out-of-order. // It isn't critical and we allow it to be delayed further if the phone sleeps, so we don't // use the alarm manager here. postDelayed(() -> { synchronized(this) { while (mBatches.size() > 0 && mBatches.keyAt(0) <= batchToken) { mBatches.removeAt(0); } } }, RETRIEVED_BATCH_DISCARD_DELAY_MS); mLastRetrievedBatchToken = batchToken; return mBatches.valueAt(index); } } Loading
services/devicepolicy/java/com/android/server/devicepolicy/NetworkLoggingHandler.java +68 −33 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.util.Log; import android.util.LongSparseArray; import com.android.internal.annotations.GuardedBy; Loading @@ -44,12 +45,21 @@ final class NetworkLoggingHandler extends Handler { // If this value changes, update DevicePolicyManager#retrieveNetworkLogs() javadoc private static final int MAX_EVENTS_PER_BATCH = 1200; private static final long BATCH_FINALIZATION_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(90); private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = TimeUnit.MINUTES.toMillis(30); /** * Maximum number of batches to store in memory. If more batches are generated and the DO * doesn't fetch them, we will discard the oldest one. */ private static final int MAX_BATCHES = 5; private static final long BATCH_FINALIZATION_TIMEOUT_MS = 90 * 60 * 1000; // 1.5h private static final long BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS = 30 * 60 * 1000; // 30m private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout"; /** Delay after which older batches get discarded after a retrieval. */ private static final long RETRIEVED_BATCH_DISCARD_DELAY_MS = 5 * 60 * 1000; // 5m private final DevicePolicyManagerService mDpm; private final AlarmManager mAlarmManager; Loading @@ -66,22 +76,27 @@ final class NetworkLoggingHandler extends Handler { static final int LOG_NETWORK_EVENT_MSG = 1; // threadsafe as it's Handler's thread confined /** Network events accumulated so far to be finalized into a batch at some point. */ @GuardedBy("this") private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>(); private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<>(); /** * Up to {@code MAX_BATCHES} finalized batches of logs ready to be retrieved by the DO. Already * retrieved batches are discarded after {@code RETRIEVED_BATCH_DISCARD_DELAY_MS}. */ @GuardedBy("this") private ArrayList<NetworkEvent> mFullBatch; private final LongSparseArray<ArrayList<NetworkEvent>> mBatches = new LongSparseArray<>(MAX_BATCHES); @GuardedBy("this") private boolean mPaused = false; // each full batch is represented by its token, which the DPC has to provide back to retrieve it @GuardedBy("this") private long mCurrentFullBatchToken; private long mCurrentBatchToken; @GuardedBy("this") private long mLastRetrievedFullBatchToken; private long mLastRetrievedBatchToken; NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) { super(looper); Loading @@ -93,7 +108,7 @@ final class NetworkLoggingHandler extends Handler { public void handleMessage(Message msg) { switch (msg.what) { case LOG_NETWORK_EVENT_MSG: { NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY); final NetworkEvent networkEvent = msg.getData().getParcelable(NETWORK_EVENT_KEY); if (networkEvent != null) { synchronized (NetworkLoggingHandler.this) { mNetworkEvents.add(networkEvent); Loading @@ -113,6 +128,8 @@ final class NetworkLoggingHandler extends Handler { void scheduleBatchFinalization() { final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS; // We use alarm manager and not just postDelayed here to ensure the batch gets finalized // even if the device goes to sleep. mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when, BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG, mBatchTimeoutAlarmListener, this); Loading @@ -131,62 +148,80 @@ final class NetworkLoggingHandler extends Handler { return; } Log.d(TAG, "Resumed network logging. Current batch=" + mCurrentFullBatchToken + ", LastRetrievedBatch=" + mLastRetrievedFullBatchToken); Log.d(TAG, "Resumed network logging. Current batch=" + mCurrentBatchToken + ", LastRetrievedBatch=" + mLastRetrievedBatchToken); mPaused = false; // If there is a full batch ready that the device owner hasn't been notified about, do it // now. if (mFullBatch != null && mFullBatch.size() > 0 && mLastRetrievedFullBatchToken != mCurrentFullBatchToken) { // If there is a batch ready that the device owner hasn't been notified about, do it now. if (mBatches.size() > 0 && mLastRetrievedBatchToken != mCurrentBatchToken) { scheduleBatchFinalization(); notifyDeviceOwnerLocked(); } } synchronized void discardLogs() { mFullBatch = null; mNetworkEvents = new ArrayList<NetworkEvent>(); mBatches.clear(); mNetworkEvents = new ArrayList<>(); Log.d(TAG, "Discarded all network logs"); } @GuardedBy("this") private void finalizeBatchAndNotifyDeviceOwnerLocked() { if (mNetworkEvents.size() > 0) { // finalize the batch and start a new one from scratch mFullBatch = mNetworkEvents; mCurrentFullBatchToken++; mNetworkEvents = new ArrayList<NetworkEvent>(); // Finalize the batch and start a new one from scratch. if (mBatches.size() >= MAX_BATCHES) { // Remove the oldest batch if we hit the limit. mBatches.removeAt(0); } mCurrentBatchToken++; mBatches.append(mCurrentBatchToken, mNetworkEvents); mNetworkEvents = new ArrayList<>(); if (!mPaused) { notifyDeviceOwnerLocked(); } } else { // don't notify the DO, since there are no events; DPC can still retrieve // Don't notify the DO, since there are no events; DPC can still retrieve // the last full batch if not paused. Log.d(TAG, "Was about to finalize the batch, but there were no events to send to" + " the DPC, the batchToken of last available batch: " + mCurrentFullBatchToken); + " the DPC, the batchToken of last available batch: " + mCurrentBatchToken); } // regardless of whether the batch was non-empty schedule a new finalization after timeout // Regardless of whether the batch was non-empty schedule a new finalization after timeout. scheduleBatchFinalization(); } /** Sends a notification to the DO. Should only be called when there is a batch available. */ @GuardedBy("this") private void notifyDeviceOwnerLocked() { Bundle extras = new Bundle(); extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken); extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size()); final Bundle extras = new Bundle(); final int lastBatchSize = mBatches.valueAt(mBatches.size() - 1).size(); extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentBatchToken); extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, lastBatchSize); Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: " + mCurrentFullBatchToken); + mCurrentBatchToken); mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras); } synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) { if (batchToken != mCurrentFullBatchToken) { synchronized List<NetworkEvent> retrieveFullLogBatch(final long batchToken) { final int index = mBatches.indexOfKey(batchToken); if (index < 0) { // Invalid token or batch has already been discarded. return null; } mLastRetrievedFullBatchToken = mCurrentFullBatchToken; return mFullBatch; // Schedule this and older batches to be discarded after a delay to lessen memory load // without interfering with the admin's ability to collect logs out-of-order. // It isn't critical and we allow it to be delayed further if the phone sleeps, so we don't // use the alarm manager here. postDelayed(() -> { synchronized(this) { while (mBatches.size() > 0 && mBatches.keyAt(0) <= batchToken) { mBatches.removeAt(0); } } }, RETRIEVED_BATCH_DISCARD_DELAY_MS); mLastRetrievedBatchToken = batchToken; return mBatches.valueAt(index); } }