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