• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.ddmuilib.logcat;
18 
19 import com.android.ddmlib.IDevice;
20 import com.android.ddmlib.Log;
21 import com.android.ddmlib.MultiLineReceiver;
22 
23 import org.eclipse.jface.preference.IPreferenceStore;
24 
25 import java.util.HashSet;
26 import java.util.List;
27 import java.util.Set;
28 
29 /**
30  * A class to monitor a device for logcat messages. It stores the received
31  * log messages in a circular buffer.
32  */
33 public final class LogCatReceiver {
34     private static final String LOGCAT_COMMAND = "logcat -v long";
35     private static final int DEVICE_POLL_INTERVAL_MSEC = 1000;
36 
37     private LogCatMessageList mLogMessages;
38     private IDevice mCurrentDevice;
39     private LogCatOutputReceiver mCurrentLogCatOutputReceiver;
40     private Set<ILogCatMessageEventListener> mLogCatMessageListeners;
41     private LogCatMessageParser mLogCatMessageParser;
42     private LogCatPidToNameMapper mPidToNameMapper;
43     private IPreferenceStore mPrefStore;
44 
45     /**
46      * Construct a LogCat message receiver for provided device. This will launch a
47      * logcat command on the device, and monitor the output of that command in
48      * a separate thread. All logcat messages are then stored in a circular
49      * buffer, which can be retrieved using {@link LogCatReceiver#getMessages()}.
50      * @param device device to monitor for logcat messages
51      * @param prefStore
52      */
LogCatReceiver(IDevice device, IPreferenceStore prefStore)53     public LogCatReceiver(IDevice device, IPreferenceStore prefStore) {
54         mCurrentDevice = device;
55         mPrefStore = prefStore;
56 
57         mLogCatMessageListeners = new HashSet<ILogCatMessageEventListener>();
58         mLogCatMessageParser = new LogCatMessageParser();
59         mPidToNameMapper = new LogCatPidToNameMapper(mCurrentDevice);
60 
61         mLogMessages = new LogCatMessageList(getFifoSize());
62 
63         startReceiverThread();
64     }
65 
66     /**
67      * Stop receiving messages from currently active device.
68      */
stop()69     public void stop() {
70         if (mCurrentLogCatOutputReceiver != null) {
71             /* stop the current logcat command */
72             mCurrentLogCatOutputReceiver.mIsCancelled = true;
73             mCurrentLogCatOutputReceiver = null;
74         }
75 
76         mLogMessages = null;
77         mCurrentDevice = null;
78     }
79 
getFifoSize()80     private int getFifoSize() {
81         int n = mPrefStore.getInt(LogCatMessageList.MAX_MESSAGES_PREFKEY);
82         return n == 0 ? LogCatMessageList.MAX_MESSAGES_DEFAULT : n;
83     }
84 
startReceiverThread()85     private void startReceiverThread() {
86         mCurrentLogCatOutputReceiver = new LogCatOutputReceiver();
87 
88         Thread t = new Thread(new Runnable() {
89             public void run() {
90                 /* wait while the device comes online */
91                 while (!mCurrentDevice.isOnline()) {
92                     try {
93                         Thread.sleep(DEVICE_POLL_INTERVAL_MSEC);
94                     } catch (InterruptedException e) {
95                         return;
96                     }
97                 }
98 
99                 try {
100                     mCurrentDevice.executeShellCommand(LOGCAT_COMMAND,
101                             mCurrentLogCatOutputReceiver, 0);
102                 } catch (Exception e) {
103                     /* There are 4 possible exceptions: TimeoutException,
104                      * AdbCommandRejectedException, ShellCommandUnresponsiveException and
105                      * IOException. In case of any of them, the only recourse is to just
106                      * log this unexpected situation and move on.
107                      */
108                     Log.e("Unexpected error while launching logcat. Try reselecting the device.",
109                             e);
110                 }
111             }
112         });
113         t.setName("LogCat output receiver for " + mCurrentDevice.getSerialNumber());
114         t.start();
115     }
116 
117     /**
118      * LogCatOutputReceiver implements {@link MultiLineReceiver#processNewLines(String[])},
119      * which is called whenever there is output from logcat. It simply redirects this output
120      * to {@link LogCatReceiver#processLogLines(String[])}. This class is expected to be
121      * used from a different thread, and the only way to stop that thread is by using the
122      * {@link LogCatOutputReceiver#mIsCancelled} variable.
123      * See {@link IDevice#executeShellCommand(String, IShellOutputReceiver, int)} for more
124      * details.
125      */
126     private class LogCatOutputReceiver extends MultiLineReceiver {
127         private boolean mIsCancelled;
128 
LogCatOutputReceiver()129         public LogCatOutputReceiver() {
130             setTrimLine(false);
131         }
132 
133         /** Implements {@link IShellOutputReceiver#isCancelled() }. */
isCancelled()134         public boolean isCancelled() {
135             return mIsCancelled;
136         }
137 
138         @Override
processNewLines(String[] lines)139         public void processNewLines(String[] lines) {
140             if (!mIsCancelled) {
141                 processLogLines(lines);
142             }
143         }
144     }
145 
processLogLines(String[] lines)146     private void processLogLines(String[] lines) {
147         List<LogCatMessage> messages = mLogCatMessageParser.processLogLines(lines,
148                 mPidToNameMapper);
149 
150         if (messages.size() > 0) {
151             for (LogCatMessage m : messages) {
152                 mLogMessages.appendMessage(m);
153             }
154             sendMessageReceivedEvent(messages);
155         }
156     }
157 
158     /**
159      * Get the list of logcat messages received from currently active device.
160      * @return list of messages if currently listening, null otherwise
161      */
getMessages()162     public LogCatMessageList getMessages() {
163         return mLogMessages;
164     }
165 
166     /**
167      * Clear the list of messages received from the currently active device.
168      */
clearMessages()169     public void clearMessages() {
170         mLogMessages.clear();
171     }
172 
173     /**
174      * Add to list of message event listeners.
175      * @param l listener to notified when messages are received from the device
176      */
addMessageReceivedEventListener(ILogCatMessageEventListener l)177     public void addMessageReceivedEventListener(ILogCatMessageEventListener l) {
178         mLogCatMessageListeners.add(l);
179     }
180 
removeMessageReceivedEventListener(ILogCatMessageEventListener l)181     public void removeMessageReceivedEventListener(ILogCatMessageEventListener l) {
182         mLogCatMessageListeners.remove(l);
183     }
184 
sendMessageReceivedEvent(List<LogCatMessage> messages)185     private void sendMessageReceivedEvent(List<LogCatMessage> messages) {
186         for (ILogCatMessageEventListener l : mLogCatMessageListeners) {
187             l.messageReceived(messages);
188         }
189     }
190 
191     /**
192      * Resize the internal FIFO.
193      * @param size new size
194      */
resizeFifo(int size)195     public void resizeFifo(int size) {
196         mLogMessages.resize(size);
197     }
198 }
199