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 package com.android.ide.eclipse.ddms;
17 
18 import com.android.ddmlib.AndroidDebugBridge;
19 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
20 import com.android.ddmlib.IDevice;
21 import com.android.ddmlib.Log.LogLevel;
22 import com.android.ddmlib.logcat.LogCatMessage;
23 import com.android.ddmuilib.logcat.ILogCatBufferChangeListener;
24 import com.android.ddmuilib.logcat.LogCatReceiver;
25 import com.android.ddmuilib.logcat.LogCatReceiverFactory;
26 import com.android.ide.eclipse.ddms.views.LogCatView;
27 
28 import org.eclipse.jface.preference.IPreferenceStore;
29 import org.eclipse.jface.util.IPropertyChangeListener;
30 import org.eclipse.jface.util.PropertyChangeEvent;
31 import org.eclipse.jface.window.Window;
32 import org.eclipse.swt.widgets.Display;
33 import org.eclipse.swt.widgets.Shell;
34 import org.eclipse.ui.IViewPart;
35 import org.eclipse.ui.IWorkbenchPage;
36 import org.eclipse.ui.IWorkbenchWindow;
37 import org.eclipse.ui.PartInitException;
38 import org.eclipse.ui.PlatformUI;
39 
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.concurrent.atomic.AtomicBoolean;
44 
45 /**
46  * LogCatMonitor helps in monitoring the logcat output from a set of devices.
47  * It scans through the received logcat messages, and activates the logcat view
48  * if any message is deemed important.
49  */
50 public class LogCatMonitor {
51     public static final String AUTO_MONITOR_PREFKEY = "ddms.logcat.automonitor"; //$NON-NLS-1$
52     public static final String AUTO_MONITOR_LOGLEVEL = "ddms.logcat.auotmonitor.level"; //$NON-NLS-1$
53     private static final String AUTO_MONITOR_PROMPT_SHOWN = "ddms.logcat.automonitor.userprompt"; //$NON-NLS-1$
54 
55     private IPreferenceStore mPrefStore;
56     private Map<String, DeviceData> mMonitoredDevices;
57     private IDebuggerConnector[] mConnectors;
58 
59     private int mMinMessagePriority;
60 
61     /**
62      * Flag that controls when the logcat stream is checked. This flag is set when the user
63      * performs a launch, and is reset as soon as the logcat view is displayed.
64      */
65     final AtomicBoolean mMonitorEnabled = new AtomicBoolean(false);
66 
LogCatMonitor(IDebuggerConnector[] debuggerConnectors, IPreferenceStore prefStore)67     public LogCatMonitor(IDebuggerConnector[] debuggerConnectors, IPreferenceStore prefStore) {
68         mConnectors = debuggerConnectors;
69         mPrefStore = prefStore;
70         mMinMessagePriority =
71                 LogLevel.getByString(mPrefStore.getString(AUTO_MONITOR_LOGLEVEL)).getPriority();
72 
73         mMonitoredDevices = new HashMap<String, DeviceData>();
74 
75         AndroidDebugBridge.addDeviceChangeListener(new IDeviceChangeListener() {
76             @Override
77             public void deviceDisconnected(IDevice device) {
78                 unmonitorDevice(device.getSerialNumber());
79                 mMonitoredDevices.remove(device.getSerialNumber());
80             }
81 
82             @Override
83             public void deviceConnected(IDevice device) {
84             }
85 
86             @Override
87             public void deviceChanged(IDevice device, int changeMask) {
88             }
89         });
90 
91         mPrefStore.addPropertyChangeListener(new IPropertyChangeListener() {
92             @Override
93             public void propertyChange(PropertyChangeEvent event) {
94                 if (AUTO_MONITOR_PREFKEY.equals(event.getProperty())
95                         && event.getNewValue().equals(false)) {
96                     unmonitorAllDevices();
97                 } else if (AUTO_MONITOR_LOGLEVEL.equals(event.getProperty())) {
98                     mMinMessagePriority =
99                             LogLevel.getByString((String) event.getNewValue()).getPriority();
100                 }
101             }
102         });
103     }
104 
unmonitorAllDevices()105     private void unmonitorAllDevices() {
106         for (String device : mMonitoredDevices.keySet()) {
107             unmonitorDevice(device);
108         }
109 
110         mMonitoredDevices.clear();
111     }
112 
unmonitorDevice(String deviceSerial)113     private void unmonitorDevice(String deviceSerial) {
114         DeviceData data = mMonitoredDevices.get(deviceSerial);
115         if (data == null) {
116             return;
117         }
118 
119         data.receiver.removeMessageReceivedEventListener(data.bufferChangeListener);
120     }
121 
monitorDevice(final IDevice device)122     public void monitorDevice(final IDevice device) {
123         if (!mPrefStore.getBoolean(AUTO_MONITOR_PREFKEY)) {
124             // do not monitor device if auto monitoring is off
125             return;
126         }
127 
128         mMonitorEnabled.set(true);
129 
130         if (mMonitoredDevices.keySet().contains(device.getSerialNumber())) {
131             // the device is already monitored
132             return;
133         }
134 
135         LogCatReceiver r = LogCatReceiverFactory.INSTANCE.newReceiver(device, mPrefStore);
136         ILogCatBufferChangeListener l = new ILogCatBufferChangeListener() {
137             @Override
138             public void bufferChanged(List<LogCatMessage> addedMessages,
139                     List<LogCatMessage> deletedMessages) {
140                 checkMessages(addedMessages, device);
141             }
142         };
143         r.addMessageReceivedEventListener(l);
144 
145         mMonitoredDevices.put(device.getSerialNumber(), new DeviceData(r, l));
146     }
147 
checkMessages(List<LogCatMessage> receivedMessages, IDevice device)148     private void checkMessages(List<LogCatMessage> receivedMessages, IDevice device) {
149         if (!mMonitorEnabled.get()) {
150             return;
151         }
152 
153         // check the received list of messages to see if any of them are
154         // significant enough to be seen by the user. If so, activate the logcat view
155         // to display those messages
156         for (LogCatMessage m : receivedMessages) {
157             if (isImportantMessage(m)) {
158                 focusLogCatView(device, m.getAppName());
159 
160                 // now that logcat view is active, no need to check messages until the next
161                 // time user launches an application.
162                 mMonitorEnabled.set(false);
163                 break;
164             }
165         }
166     }
167 
168     /**
169      * Check whether a message is "important". Currently, we assume that a message is important if
170      * it is of severity level error or higher, and it belongs to an app currently in the workspace.
171      */
isImportantMessage(LogCatMessage m)172     private boolean isImportantMessage(LogCatMessage m) {
173         if (m.getLogLevel().getPriority() < mMinMessagePriority) {
174             return false;
175         }
176 
177         String app = m.getAppName();
178         for (IDebuggerConnector c : mConnectors) {
179             if (c.isWorkspaceApp(app)) {
180                 return true;
181             }
182         }
183 
184         return false;
185     }
186 
focusLogCatView(final IDevice device, final String appName)187     private void focusLogCatView(final IDevice device, final String appName) {
188         Display.getDefault().asyncExec(new Runnable() {
189             @Override
190             public void run() {
191                 IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
192                 if (window == null) {
193                     return;
194                 }
195 
196                 IWorkbenchPage page = window.getActivePage();
197                 if (page == null) {
198                     return;
199                 }
200 
201                 // if the logcat view is not visible, then prompt the user once to set
202                 // logcat monitoring preferences
203                 if (!isLogCatViewVisible(page)) {
204                     boolean showLogCatView = promptUserOnce(page.getWorkbenchWindow().getShell());
205                     if (!showLogCatView) {
206                         return;
207                     }
208                 }
209 
210                 // display view
211                 final LogCatView v = displayLogCatView(page);
212                 if (v == null) {
213                     return;
214                 }
215 
216                 // select correct device
217                 v.selectionChanged(device);
218 
219                 // select appropriate filter
220                 v.selectTransientAppFilter(appName);
221             }
222 
223             private boolean isLogCatViewVisible(IWorkbenchPage page) {
224                 IViewPart view = page.findView(LogCatView.ID);
225                 return view != null && page.isPartVisible(view);
226             }
227 
228             private LogCatView displayLogCatView(IWorkbenchPage page) {
229                 // if the view is already in the page, just bring it to the front
230                 // without giving it focus.
231                 IViewPart view = page.findView(LogCatView.ID);
232                 if (view != null) {
233                     page.bringToTop(view);
234                     if (view instanceof LogCatView) {
235                         return (LogCatView)view;
236                     }
237                 }
238 
239                 // if the view is not in the page, then create and show it.
240                 try {
241                     return (LogCatView) page.showView(LogCatView.ID);
242                 } catch (PartInitException e) {
243                     return null;
244                 }
245             }
246 
247             private boolean promptUserOnce(Shell shell) {
248                 // see if this prompt was already displayed
249                 boolean promptShown = mPrefStore.getBoolean(AUTO_MONITOR_PROMPT_SHOWN);
250                 if (promptShown) {
251                     return mPrefStore.getBoolean(AUTO_MONITOR_PREFKEY);
252                 }
253 
254                 LogCatMonitorDialog dlg = new LogCatMonitorDialog(shell);
255                 int r = dlg.open();
256 
257                 // save preference indicating that this dialog has been displayed once
258                 mPrefStore.setValue(AUTO_MONITOR_PROMPT_SHOWN, true);
259                 mPrefStore.setValue(AUTO_MONITOR_PREFKEY, dlg.shouldMonitor());
260                 mPrefStore.setValue(AUTO_MONITOR_LOGLEVEL, dlg.getMinimumPriority());
261 
262                 return r == Window.OK && dlg.shouldMonitor();
263             }
264 
265         });
266     }
267 
268     private static class DeviceData {
269         public final LogCatReceiver receiver;
270         public final ILogCatBufferChangeListener bufferChangeListener;
271 
DeviceData(LogCatReceiver r, ILogCatBufferChangeListener l)272         public DeviceData(LogCatReceiver r, ILogCatBufferChangeListener l) {
273             receiver = r;
274             bufferChangeListener = l;
275         }
276     }
277 }
278