/*
* 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.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.NetworkEvent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
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";
// 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);
private static final String NETWORK_LOGGING_TIMEOUT_ALARM_TAG = "NetworkLogging.batchTimeout";
private final DevicePolicyManagerService mDpm;
private final AlarmManager mAlarmManager;
private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
@Override
public void onAlarm() {
Log.d(TAG, "Received a batch finalization timeout alarm, finalizing "
+ mNetworkEvents.size() + " pending events.");
synchronized (NetworkLoggingHandler.this) {
finalizeBatchAndNotifyDeviceOwnerIfNotEmpty();
}
}
};
static final int LOG_NETWORK_EVENT_MSG = 1;
// threadsafe as it's Handler's thread confined
@GuardedBy("this")
private ArrayList<NetworkEvent> mNetworkEvents = new ArrayList<NetworkEvent>();
@GuardedBy("this")
private ArrayList<NetworkEvent> mFullBatch;
// each full batch is represented by its token, which the DPC has to provide back to revieve it
@GuardedBy("this")
private long mCurrentFullBatchToken;
NetworkLoggingHandler(Looper looper, DevicePolicyManagerService dpm) {
super(looper);
mDpm = dpm;
mAlarmManager = mDpm.mInjector.getAlarmManager();
}
@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) {
finalizeBatchAndNotifyDeviceOwnerIfNotEmpty();
}
}
break;
}
default: {
Log.d(TAG, "NetworkLoggingHandler received an unknown of message.");
break;
}
}
}
void scheduleBatchFinalization() {
final long when = SystemClock.elapsedRealtime() + BATCH_FINALIZATION_TIMEOUT_MS;
mAlarmManager.setWindow(AlarmManager.ELAPSED_REALTIME_WAKEUP, when,
BATCH_FINALIZATION_TIMEOUT_ALARM_INTERVAL_MS, NETWORK_LOGGING_TIMEOUT_ALARM_TAG,
mBatchTimeoutAlarmListener, this);
Log.d(TAG, "Scheduled a new batch finalization alarm " + BATCH_FINALIZATION_TIMEOUT_MS
+ "ms from now.");
}
private synchronized void finalizeBatchAndNotifyDeviceOwnerIfNotEmpty() {
if (mNetworkEvents.size() > 0) {
// finalize the batch and start a new one from scratch
mFullBatch = mNetworkEvents;
mCurrentFullBatchToken++;
mNetworkEvents = new ArrayList<NetworkEvent>();
// notify DO that there's a new non-empty batch waiting
Bundle extras = new Bundle();
extras.putLong(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_TOKEN, mCurrentFullBatchToken);
extras.putInt(DeviceAdminReceiver.EXTRA_NETWORK_LOGS_COUNT, mFullBatch.size());
Log.d(TAG, "Sending network logging batch broadcast to device owner, batchToken: "
+ mCurrentFullBatchToken);
mDpm.sendDeviceOwnerCommand(DeviceAdminReceiver.ACTION_NETWORK_LOGS_AVAILABLE, extras);
} else {
// don't notify the DO, since there are no events; DPC can still retrieve
// the last full batch
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);
}
// regardless of whether the batch was non-empty schedule a new finalization after timeout
scheduleBatchFinalization();
}
synchronized List<NetworkEvent> retrieveFullLogBatch(long batchToken) {
if (batchToken != mCurrentFullBatchToken) {
return null;
}
return mFullBatch;
}
}