Merge "Store up to 5 network log batches if needed." into oc-dev

This commit is contained in:
Pavel Grafov
2017-03-28 17:15:53 +00:00
committed by Android (Google) Code Review

View File

@@ -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;
@@ -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;
@@ -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);
@@ -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);
@@ -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);
@@ -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);
}
}