/* * 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.ide.eclipse.ddms; import com.android.ddmlib.AndroidDebugBridge; import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; import com.android.ddmlib.IDevice; import com.android.ddmlib.Log.LogLevel; import com.android.ddmuilib.logcat.ILogCatBufferChangeListener; import com.android.ddmuilib.logcat.LogCatMessage; import com.android.ddmuilib.logcat.LogCatReceiver; import com.android.ddmuilib.logcat.LogCatReceiverFactory; import com.android.ide.eclipse.ddms.views.LogCatView; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; /** * LogCatMonitor helps in monitoring the logcat output from a set of devices. * It scans through the received logcat messages, and activates the logcat view * if any message is deemed important. */ public class LogCatMonitor { public static final String AUTO_MONITOR_PREFKEY = "ddms.logcat.automonitor"; //$NON-NLS-1$ public static final String AUTO_MONITOR_LOGLEVEL = "ddms.logcat.auotmonitor.level"; //$NON-NLS-1$ private static final String AUTO_MONITOR_PROMPT_SHOWN = "ddms.logcat.automonitor.userprompt"; //$NON-NLS-1$ private IPreferenceStore mPrefStore; private Map<String, DeviceData> mMonitoredDevices; private IDebuggerConnector[] mConnectors; private int mMinMessagePriority; /** * Flag that controls when the logcat stream is checked. This flag is set when the user * performs a launch, and is reset as soon as the logcat view is displayed. */ final AtomicBoolean mMonitorEnabled = new AtomicBoolean(false); public LogCatMonitor(IDebuggerConnector[] debuggerConnectors, IPreferenceStore prefStore) { mConnectors = debuggerConnectors; mPrefStore = prefStore; mMinMessagePriority = LogLevel.getByString(mPrefStore.getString(AUTO_MONITOR_LOGLEVEL)).getPriority(); mMonitoredDevices = new HashMap<String, DeviceData>(); AndroidDebugBridge.addDeviceChangeListener(new IDeviceChangeListener() { @Override public void deviceDisconnected(IDevice device) { unmonitorDevice(device.getSerialNumber()); mMonitoredDevices.remove(device.getSerialNumber()); } @Override public void deviceConnected(IDevice device) { } @Override public void deviceChanged(IDevice device, int changeMask) { } }); mPrefStore.addPropertyChangeListener(new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (AUTO_MONITOR_PREFKEY.equals(event.getProperty()) && event.getNewValue().equals(false)) { unmonitorAllDevices(); } else if (AUTO_MONITOR_LOGLEVEL.equals(event.getProperty())) { mMinMessagePriority = LogLevel.getByString((String) event.getNewValue()).getPriority(); } } }); } private void unmonitorAllDevices() { for (String device : mMonitoredDevices.keySet()) { unmonitorDevice(device); } mMonitoredDevices.clear(); } private void unmonitorDevice(String deviceSerial) { DeviceData data = mMonitoredDevices.get(deviceSerial); if (data == null) { return; } data.receiver.removeMessageReceivedEventListener(data.bufferChangeListener); } public void monitorDevice(final IDevice device) { if (!mPrefStore.getBoolean(AUTO_MONITOR_PREFKEY)) { // do not monitor device if auto monitoring is off return; } mMonitorEnabled.set(true); if (mMonitoredDevices.keySet().contains(device.getSerialNumber())) { // the device is already monitored return; } LogCatReceiver r = LogCatReceiverFactory.INSTANCE.newReceiver(device, mPrefStore); ILogCatBufferChangeListener l = new ILogCatBufferChangeListener() { @Override public void bufferChanged(List<LogCatMessage> addedMessages, List<LogCatMessage> deletedMessages) { checkMessages(addedMessages, device); } }; r.addMessageReceivedEventListener(l); mMonitoredDevices.put(device.getSerialNumber(), new DeviceData(r, l)); } private void checkMessages(List<LogCatMessage> receivedMessages, IDevice device) { if (!mMonitorEnabled.get()) { return; } // check the received list of messages to see if any of them are // significant enough to be seen by the user. If so, activate the logcat view // to display those messages for (LogCatMessage m : receivedMessages) { if (isImportantMessage(m)) { focusLogCatView(device, m.getAppName()); // now that logcat view is active, no need to check messages until the next // time user launches an application. mMonitorEnabled.set(false); break; } } } /** * Check whether a message is "important". Currently, we assume that a message is important if * it is of severity level error or higher, and it belongs to an app currently in the workspace. */ private boolean isImportantMessage(LogCatMessage m) { if (m.getLogLevel().getPriority() < mMinMessagePriority) { return false; } String app = m.getAppName(); for (IDebuggerConnector c : mConnectors) { if (c.isWorkspaceApp(app)) { return true; } } return false; } private void focusLogCatView(final IDevice device, final String appName) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { IWorkbenchWindow window = PlatformUI.getWorkbench().getActiveWorkbenchWindow(); if (window == null) { return; } IWorkbenchPage page = window.getActivePage(); if (page == null) { return; } // if the logcat view is not visible, then prompt the user once to set // logcat monitoring preferences if (!isLogCatViewVisible(page)) { boolean showLogCatView = promptUserOnce(page.getWorkbenchWindow().getShell()); if (!showLogCatView) { return; } } // display view final LogCatView v = displayLogCatView(page); if (v == null) { return; } // select correct device v.selectionChanged(device); // select appropriate filter v.selectTransientAppFilter(appName); } private boolean isLogCatViewVisible(IWorkbenchPage page) { IViewPart view = page.findView(LogCatView.ID); return view != null && page.isPartVisible(view); } private LogCatView displayLogCatView(IWorkbenchPage page) { // if the view is already in the page, just bring it to the front // without giving it focus. IViewPart view = page.findView(LogCatView.ID); if (view != null) { page.bringToTop(view); if (view instanceof LogCatView) { return (LogCatView)view; } } // if the view is not in the page, then create and show it. try { return (LogCatView) page.showView(LogCatView.ID); } catch (PartInitException e) { return null; } } private boolean promptUserOnce(Shell shell) { // see if this prompt was already displayed boolean promptShown = mPrefStore.getBoolean(AUTO_MONITOR_PROMPT_SHOWN); if (promptShown) { return mPrefStore.getBoolean(AUTO_MONITOR_PREFKEY); } LogCatMonitorDialog dlg = new LogCatMonitorDialog(shell); int r = dlg.open(); // save preference indicating that this dialog has been displayed once mPrefStore.setValue(AUTO_MONITOR_PROMPT_SHOWN, true); mPrefStore.setValue(AUTO_MONITOR_PREFKEY, dlg.shouldMonitor()); mPrefStore.setValue(AUTO_MONITOR_LOGLEVEL, dlg.getMinimumPriority()); return r == Window.OK && dlg.shouldMonitor(); } }); } private static class DeviceData { public final LogCatReceiver receiver; public final ILogCatBufferChangeListener bufferChangeListener; public DeviceData(LogCatReceiver r, ILogCatBufferChangeListener l) { receiver = r; bufferChangeListener = l; } } }