Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8f8fa662 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Store up to 5 network log batches if needed."

parents f84df9c7 48733074
Loading
Loading
Loading
Loading
+68 −33
Original line number Diff line number Diff line
@@ -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);
    }
}