/*
* Copyright (C) 2011 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.ddmuilib.logcat;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.Log;
import com.android.ddmlib.Log.LogLevel;
import com.android.ddmlib.MultiLineReceiver;
import org.eclipse.jface.preference.IPreferenceStore;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A class to monitor a device for logcat messages. It stores the received
* log messages in a circular buffer.
*/
public final class LogCatReceiver {
private static final String LOGCAT_COMMAND = "logcat -v long";
private static final int DEVICE_POLL_INTERVAL_MSEC = 1000;
private static LogCatMessage DEVICE_DISCONNECTED_MESSAGE =
new LogCatMessage(LogLevel.ERROR, "", "", "",
"", "", "Device disconnected");
private LogCatMessageList mLogMessages;
private IDevice mCurrentDevice;
private LogCatOutputReceiver mCurrentLogCatOutputReceiver;
private Set<ILogCatBufferChangeListener> mLogCatMessageListeners;
private LogCatMessageParser mLogCatMessageParser;
private LogCatPidToNameMapper mPidToNameMapper;
private IPreferenceStore mPrefStore;
/**
* Construct a LogCat message receiver for provided device. This will launch a
* logcat command on the device, and monitor the output of that command in
* a separate thread. All logcat messages are then stored in a circular
* buffer, which can be retrieved using {@link LogCatReceiver#getMessages()}.
* @param device device to monitor for logcat messages
* @param prefStore
*/
public LogCatReceiver(IDevice device, IPreferenceStore prefStore) {
mCurrentDevice = device;
mPrefStore = prefStore;
mLogCatMessageListeners = new HashSet<ILogCatBufferChangeListener>();
mLogCatMessageParser = new LogCatMessageParser();
mPidToNameMapper = new LogCatPidToNameMapper(mCurrentDevice);
mLogMessages = new LogCatMessageList(getFifoSize());
startReceiverThread();
}
/**
* Stop receiving messages from currently active device.
*/
public void stop() {
if (mCurrentLogCatOutputReceiver != null) {
/* stop the current logcat command */
mCurrentLogCatOutputReceiver.mIsCancelled = true;
mCurrentLogCatOutputReceiver = null;
// add a message to the log indicating that the device has been disconnected.
processLogMessages(Collections.singletonList(DEVICE_DISCONNECTED_MESSAGE));
}
mCurrentDevice = null;
}
private int getFifoSize() {
int n = mPrefStore.getInt(LogCatMessageList.MAX_MESSAGES_PREFKEY);
return n == 0 ? LogCatMessageList.MAX_MESSAGES_DEFAULT : n;
}
private void startReceiverThread() {
mCurrentLogCatOutputReceiver = new LogCatOutputReceiver();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
/* wait while the device comes online */
while (mCurrentDevice != null && !mCurrentDevice.isOnline()) {
try {
Thread.sleep(DEVICE_POLL_INTERVAL_MSEC);
} catch (InterruptedException e) {
return;
}
}
try {
if (mCurrentDevice != null) {
mCurrentDevice.executeShellCommand(LOGCAT_COMMAND,
mCurrentLogCatOutputReceiver, 0);
}
} catch (Exception e) {
/* There are 4 possible exceptions: TimeoutException,
* AdbCommandRejectedException, ShellCommandUnresponsiveException and
* IOException. In case of any of them, the only recourse is to just
* log this unexpected situation and move on.
*/
Log.e("Unexpected error while launching logcat. Try reselecting the device.",
e);
}
}
});
t.setName("LogCat output receiver for " + mCurrentDevice.getSerialNumber());
t.start();
}
/**
* LogCatOutputReceiver implements {@link MultiLineReceiver#processNewLines(String[])},
* which is called whenever there is output from logcat. It simply redirects this output
* to {@link LogCatReceiver#processLogLines(String[])}. This class is expected to be
* used from a different thread, and the only way to stop that thread is by using the
* {@link LogCatOutputReceiver#mIsCancelled} variable.
* See {@link IDevice#executeShellCommand(String, IShellOutputReceiver, int)} for more
* details.
*/
private class LogCatOutputReceiver extends MultiLineReceiver {
private boolean mIsCancelled;
public LogCatOutputReceiver() {
setTrimLine(false);
}
/** Implements {@link IShellOutputReceiver#isCancelled() }. */
@Override
public boolean isCancelled() {
return mIsCancelled;
}
@Override
public void processNewLines(String[] lines) {
if (!mIsCancelled) {
processLogLines(lines);
}
}
}
private void processLogLines(String[] lines) {
List<LogCatMessage> newMessages = mLogCatMessageParser.processLogLines(lines,
mPidToNameMapper);
processLogMessages(newMessages);
}
private void processLogMessages(List<LogCatMessage> newMessages) {
if (newMessages.size() > 0) {
List<LogCatMessage> deletedMessages;
synchronized (mLogMessages) {
deletedMessages = mLogMessages.ensureSpace(newMessages.size());
mLogMessages.appendMessages(newMessages);
}
sendLogChangedEvent(newMessages, deletedMessages);
}
}
/**
* Get the list of logcat messages received from currently active device.
* @return list of messages if currently listening, null otherwise
*/
public LogCatMessageList getMessages() {
return mLogMessages;
}
/**
* Clear the list of messages received from the currently active device.
*/
public void clearMessages() {
mLogMessages.clear();
}
/**
* Add to list of message event listeners.
* @param l listener to notified when messages are received from the device
*/
public void addMessageReceivedEventListener(ILogCatBufferChangeListener l) {
mLogCatMessageListeners.add(l);
}
public void removeMessageReceivedEventListener(ILogCatBufferChangeListener l) {
mLogCatMessageListeners.remove(l);
}
private void sendLogChangedEvent(List<LogCatMessage> addedMessages,
List<LogCatMessage> deletedMessages) {
for (ILogCatBufferChangeListener l : mLogCatMessageListeners) {
l.bufferChanged(addedMessages, deletedMessages);
}
}
/**
* Resize the internal FIFO.
* @param size new size
*/
public void resizeFifo(int size) {
mLogMessages.resize(size);
}
}