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