| /* |
| * Copyright (C) 2007 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.Client; |
| import com.android.ddmlib.DdmPreferences; |
| import com.android.ddmlib.IDevice; |
| import com.android.ddmlib.Log; |
| import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener; |
| import com.android.ddmlib.Log.ILogOutput; |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.ddmuilib.DdmUiPreferences; |
| import com.android.ddmuilib.DevicePanel.IUiSelectionListener; |
| import com.android.ide.eclipse.ddms.preferences.PreferenceInitializer; |
| import com.android.ide.eclipse.ddms.views.DeviceView; |
| |
| import org.eclipse.core.runtime.Preferences; |
| import org.eclipse.core.runtime.Preferences.IPropertyChangeListener; |
| import org.eclipse.core.runtime.Preferences.PropertyChangeEvent; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.swt.SWTException; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IWorkbench; |
| import org.eclipse.ui.console.ConsolePlugin; |
| import org.eclipse.ui.console.IConsole; |
| import org.eclipse.ui.console.MessageConsole; |
| import org.eclipse.ui.console.MessageConsoleStream; |
| import org.eclipse.ui.plugin.AbstractUIPlugin; |
| import org.osgi.framework.BundleContext; |
| |
| import java.util.ArrayList; |
| import java.util.Calendar; |
| |
| /** |
| * The activator class controls the plug-in life cycle |
| */ |
| public final class DdmsPlugin extends AbstractUIPlugin implements IDeviceChangeListener, |
| IUiSelectionListener { |
| |
| // The plug-in ID |
| public static final String PLUGIN_ID = "com.android.ide.eclipse.ddms"; // $NON-NLS-1$ |
| |
| private static final String ADB_LOCATION = PLUGIN_ID + ".adb"; // $NON-NLS-1$ |
| |
| /** The singleton instance */ |
| private static DdmsPlugin sPlugin; |
| |
| /** Location of the adb command line executable */ |
| private static String sAdbLocation; |
| |
| /** |
| * Debug Launcher for already running apps |
| */ |
| private static IDebugLauncher sRunningAppDebugLauncher; |
| |
| /** Console for DDMS log message */ |
| private MessageConsole mDdmsConsole; |
| |
| /** Image loader object */ |
| private ImageLoader mLoader; |
| |
| private IDevice mCurrentDevice; |
| private Client mCurrentClient; |
| private boolean mListeningToUiSelection = false; |
| |
| private final ArrayList<ISelectionListener> mListeners = new ArrayList<ISelectionListener>(); |
| |
| private Color mRed; |
| |
| private boolean mDdmlibInitialized; |
| |
| /** |
| * Interface to provide debugger launcher for running apps. |
| */ |
| public interface IDebugLauncher { |
| public boolean debug(String packageName, int port); |
| } |
| |
| /** |
| * Classes which implement this interface provide methods that deals |
| * with {@link IDevice} and {@link Client} selectionchanges. |
| */ |
| public interface ISelectionListener { |
| |
| /** |
| * Sent when a new {@link Client} is selected. |
| * @param selectedClient The selected client. If null, no clients are selected. |
| */ |
| public void selectionChanged(Client selectedClient); |
| |
| /** |
| * Sent when a new {@link IDevice} is selected. |
| * @param selectedDevice the selected device. If null, no devices are selected. |
| */ |
| public void selectionChanged(IDevice selectedDevice); |
| } |
| |
| /** |
| * The constructor |
| */ |
| public DdmsPlugin() { |
| sPlugin = this; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext) |
| */ |
| @Override |
| public void start(BundleContext context) throws Exception { |
| super.start(context); |
| |
| final Display display = getDisplay(); |
| |
| // get the eclipse store |
| final IPreferenceStore eclipseStore = getPreferenceStore(); |
| |
| AndroidDebugBridge.addDeviceChangeListener(this); |
| |
| DdmUiPreferences.setStore(eclipseStore); |
| |
| //DdmUiPreferences.displayCharts(); |
| |
| // set the consoles. |
| mDdmsConsole = new MessageConsole("DDMS", null); // $NON-NLS-1$ |
| ConsolePlugin.getDefault().getConsoleManager().addConsoles( |
| new IConsole[] { |
| mDdmsConsole |
| }); |
| |
| final MessageConsoleStream consoleStream = mDdmsConsole.newMessageStream(); |
| final MessageConsoleStream errorConsoleStream = mDdmsConsole.newMessageStream(); |
| mRed = new Color(display, 0xFF, 0x00, 0x00); |
| |
| // because this can be run, in some cases, by a non UI thread, and because |
| // changing the console properties update the UI, we need to make this change |
| // in the UI thread. |
| display.asyncExec(new Runnable() { |
| public void run() { |
| errorConsoleStream.setColor(mRed); |
| } |
| }); |
| |
| // set up the ddms log to use the ddms console. |
| Log.setLogOutput(new ILogOutput() { |
| public void printLog(LogLevel logLevel, String tag, String message) { |
| if (logLevel.getPriority() >= LogLevel.ERROR.getPriority()) { |
| printToStream(errorConsoleStream, tag, message); |
| ConsolePlugin.getDefault().getConsoleManager().showConsoleView(mDdmsConsole); |
| } else { |
| printToStream(consoleStream, tag, message); |
| } |
| } |
| |
| public void printAndPromptLog(final LogLevel logLevel, final String tag, |
| final String message) { |
| printLog(logLevel, tag, message); |
| // dialog box only run in UI thread.. |
| display.asyncExec(new Runnable() { |
| public void run() { |
| Shell shell = display.getActiveShell(); |
| if (logLevel == LogLevel.ERROR) { |
| MessageDialog.openError(shell, tag, message); |
| } else { |
| MessageDialog.openWarning(shell, tag, message); |
| } |
| } |
| }); |
| } |
| |
| }); |
| |
| // create the loader that's able to load the images |
| mLoader = new ImageLoader(this); |
| |
| // set the listener for the preference change |
| Preferences prefs = getPluginPreferences(); |
| prefs.addPropertyChangeListener(new IPropertyChangeListener() { |
| public void propertyChange(PropertyChangeEvent event) { |
| // get the name of the property that changed. |
| String property = event.getProperty(); |
| |
| if (PreferenceInitializer.ATTR_DEBUG_PORT_BASE.equals(property)) { |
| DdmPreferences.setDebugPortBase( |
| eclipseStore.getInt(PreferenceInitializer.ATTR_DEBUG_PORT_BASE)); |
| } else if (PreferenceInitializer.ATTR_SELECTED_DEBUG_PORT.equals(property)) { |
| DdmPreferences.setSelectedDebugPort( |
| eclipseStore.getInt(PreferenceInitializer.ATTR_SELECTED_DEBUG_PORT)); |
| } else if (PreferenceInitializer.ATTR_THREAD_INTERVAL.equals(property)) { |
| DdmUiPreferences.setThreadRefreshInterval( |
| eclipseStore.getInt(PreferenceInitializer.ATTR_THREAD_INTERVAL)); |
| } else if (PreferenceInitializer.ATTR_LOG_LEVEL.equals(property)) { |
| DdmPreferences.setLogLevel( |
| eclipseStore.getString(PreferenceInitializer.ATTR_LOG_LEVEL)); |
| } |
| } |
| }); |
| |
| // read the adb location from the prefs to attempt to start it properly without |
| // having to wait for ADT to start |
| sAdbLocation = eclipseStore.getString(ADB_LOCATION); |
| |
| // start it in a thread to return from start() asap. |
| new Thread() { |
| @Override |
| public void run() { |
| // init ddmlib if needed |
| getDefault().initDdmlib(); |
| |
| // create and start the first bridge |
| AndroidDebugBridge.createBridge(sAdbLocation, true /* forceNewBridge */); |
| } |
| }.start(); |
| } |
| |
| public static Display getDisplay() { |
| IWorkbench bench = sPlugin.getWorkbench(); |
| if (bench != null) { |
| return bench.getDisplay(); |
| } |
| return null; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext) |
| */ |
| @Override |
| public void stop(BundleContext context) throws Exception { |
| AndroidDebugBridge.removeDeviceChangeListener(this); |
| |
| AndroidDebugBridge.terminate(); |
| |
| mRed.dispose(); |
| |
| sPlugin = null; |
| super.stop(context); |
| } |
| |
| /** |
| * Returns the shared instance |
| * |
| * @return the shared instance |
| */ |
| public static DdmsPlugin getDefault() { |
| return sPlugin; |
| } |
| |
| /** Return the image loader for the plugin */ |
| public static ImageLoader getImageLoader() { |
| if (sPlugin != null) { |
| return sPlugin.mLoader; |
| } |
| return null; |
| } |
| |
| public static String getAdb() { |
| return sAdbLocation; |
| } |
| |
| /** |
| * Set the location of the adb executable and optionally starts adb |
| * @param adb location of adb |
| * @param startAdb flag to start adb |
| */ |
| public static void setAdb(String adb, boolean startAdb) { |
| sAdbLocation = adb; |
| |
| // store the location for future ddms only start. |
| sPlugin.getPreferenceStore().setValue(ADB_LOCATION, sAdbLocation); |
| |
| // starts the server in a thread in case this is blocking. |
| if (startAdb) { |
| new Thread() { |
| @Override |
| public void run() { |
| // init ddmlib if needed |
| getDefault().initDdmlib(); |
| |
| // create and start the bridge |
| AndroidDebugBridge.createBridge(sAdbLocation, false /* forceNewBridge */); |
| } |
| }.start(); |
| } |
| } |
| |
| private synchronized void initDdmlib() { |
| if (mDdmlibInitialized == false) { |
| // set the preferences. |
| PreferenceInitializer.setupPreferences(); |
| |
| // init the lib |
| AndroidDebugBridge.init(true /* debugger support */); |
| |
| mDdmlibInitialized = true; |
| } |
| } |
| |
| /** |
| * Sets the launcher responsible for connecting the debugger to running applications. |
| * @param launcher The launcher. |
| */ |
| public static void setRunningAppDebugLauncher(IDebugLauncher launcher) { |
| sRunningAppDebugLauncher = launcher; |
| |
| // if the process view is already running, give it the launcher. |
| // This method could be called from a non ui thread, so we make sure to do that |
| // in the ui thread. |
| Display display = getDisplay(); |
| if (display != null && display.isDisposed() == false) { |
| display.asyncExec(new Runnable() { |
| public void run() { |
| DeviceView dv = DeviceView.getInstance(); |
| if (dv != null) { |
| dv.setDebugLauncher(sRunningAppDebugLauncher); |
| } |
| } |
| }); |
| } |
| } |
| |
| public static IDebugLauncher getRunningAppDebugLauncher() { |
| return sRunningAppDebugLauncher; |
| } |
| |
| public synchronized void addSelectionListener(ISelectionListener listener) { |
| mListeners.add(listener); |
| |
| // notify the new listener of the current selection |
| listener.selectionChanged(mCurrentDevice); |
| listener.selectionChanged(mCurrentClient); |
| } |
| |
| public synchronized void removeSelectionListener(ISelectionListener listener) { |
| mListeners.remove(listener); |
| } |
| |
| public synchronized void setListeningState(boolean state) { |
| mListeningToUiSelection = state; |
| } |
| |
| /** |
| * Sent when the a device is connected to the {@link AndroidDebugBridge}. |
| * <p/> |
| * This is sent from a non UI thread. |
| * @param device the new device. |
| * |
| * @see IDeviceChangeListener#deviceConnected(IDevice) |
| */ |
| public void deviceConnected(IDevice device) { |
| // if we are listening to selection coming from the ui, then we do nothing, as |
| // any change in the devices/clients, will be handled by the UI, and we'll receive |
| // selection notification through our implementation of IUiSelectionListener. |
| if (mListeningToUiSelection == false) { |
| if (mCurrentDevice == null) { |
| handleDefaultSelection(device); |
| } |
| } |
| } |
| |
| /** |
| * Sent when the a device is disconnected to the {@link AndroidDebugBridge}. |
| * <p/> |
| * This is sent from a non UI thread. |
| * @param device the new device. |
| * |
| * @see IDeviceChangeListener#deviceDisconnected(IDevice) |
| */ |
| public void deviceDisconnected(IDevice device) { |
| // if we are listening to selection coming from the ui, then we do nothing, as |
| // any change in the devices/clients, will be handled by the UI, and we'll receive |
| // selection notification through our implementation of IUiSelectionListener. |
| if (mListeningToUiSelection == false) { |
| // test if the disconnected device was the default selection. |
| if (mCurrentDevice == device) { |
| // try to find a new device |
| AndroidDebugBridge bridge = AndroidDebugBridge.getBridge(); |
| if (bridge != null) { |
| // get the device list |
| IDevice[] devices = bridge.getDevices(); |
| |
| // check if we still have devices |
| if (devices.length == 0) { |
| handleDefaultSelection((IDevice)null); |
| } else { |
| handleDefaultSelection(devices[0]); |
| } |
| } else { |
| handleDefaultSelection((IDevice)null); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sent when a device data changed, or when clients are started/terminated on the device. |
| * <p/> |
| * This is sent from a non UI thread. |
| * @param device the device that was updated. |
| * @param changeMask the mask indicating what changed. |
| * |
| * @see IDeviceChangeListener#deviceChanged(IDevice) |
| */ |
| public void deviceChanged(IDevice device, int changeMask) { |
| // if we are listening to selection coming from the ui, then we do nothing, as |
| // any change in the devices/clients, will be handled by the UI, and we'll receive |
| // selection notification through our implementation of IUiSelectionListener. |
| if (mListeningToUiSelection == false) { |
| |
| // check if this is our device |
| if (device == mCurrentDevice) { |
| if (mCurrentClient == null) { |
| handleDefaultSelection(device); |
| } else { |
| // get the clients and make sure ours is still in there. |
| Client[] clients = device.getClients(); |
| boolean foundClient = false; |
| for (Client client : clients) { |
| if (client == mCurrentClient) { |
| foundClient = true; |
| break; |
| } |
| } |
| |
| // if we haven't found our client, lets look for a new one |
| if (foundClient == false) { |
| mCurrentClient = null; |
| handleDefaultSelection(device); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sent when a new {@link IDevice} and {@link Client} are selected. |
| * @param selectedDevice the selected device. If null, no devices are selected. |
| * @param selectedClient The selected client. If null, no clients are selected. |
| */ |
| public synchronized void selectionChanged(IDevice selectedDevice, Client selectedClient) { |
| if (mCurrentDevice != selectedDevice) { |
| mCurrentDevice = selectedDevice; |
| |
| // notify of the new default device |
| for (ISelectionListener listener : mListeners) { |
| listener.selectionChanged(mCurrentDevice); |
| } |
| } |
| |
| if (mCurrentClient != selectedClient) { |
| mCurrentClient = selectedClient; |
| |
| // notify of the new default client |
| for (ISelectionListener listener : mListeners) { |
| listener.selectionChanged(mCurrentClient); |
| } |
| } |
| } |
| |
| /** |
| * Handles a default selection of a {@link IDevice} and {@link Client}. |
| * @param device the selected device |
| */ |
| private void handleDefaultSelection(final IDevice device) { |
| // because the listener expect to receive this from the UI thread, and this is called |
| // from the AndroidDebugBridge notifications, we need to run this in the UI thread. |
| try { |
| Display display = getDisplay(); |
| |
| display.asyncExec(new Runnable() { |
| public void run() { |
| // set the new device if different. |
| boolean newDevice = false; |
| if (mCurrentDevice != device) { |
| mCurrentDevice = device; |
| newDevice = true; |
| |
| // notify of the new default device |
| for (ISelectionListener listener : mListeners) { |
| listener.selectionChanged(mCurrentDevice); |
| } |
| } |
| |
| if (device != null) { |
| // if this is a device switch or the same device but we didn't find a valid |
| // client the last time, we go look for a client to use again. |
| if (newDevice || mCurrentClient == null) { |
| // now get the new client |
| Client[] clients = device.getClients(); |
| if (clients.length > 0) { |
| handleDefaultSelection(clients[0]); |
| } else { |
| handleDefaultSelection((Client)null); |
| } |
| } |
| } else { |
| handleDefaultSelection((Client)null); |
| } |
| } |
| }); |
| } catch (SWTException e) { |
| // display is disposed. Do nothing since we're quitting anyway. |
| } |
| } |
| |
| private void handleDefaultSelection(Client client) { |
| mCurrentClient = client; |
| |
| // notify of the new default client |
| for (ISelectionListener listener : mListeners) { |
| listener.selectionChanged(mCurrentClient); |
| } |
| } |
| |
| /** |
| * Prints a message, associated with a project to the specified stream |
| * @param stream The stream to write to |
| * @param tag The tag associated to the message. Can be null |
| * @param message The message to print. |
| */ |
| private static synchronized void printToStream(MessageConsoleStream stream, String tag, |
| String message) { |
| String dateTag = getMessageTag(tag); |
| |
| stream.print(dateTag); |
| stream.println(message); |
| } |
| |
| /** |
| * Creates a string containing the current date/time, and the tag |
| * @param tag The tag associated to the message. Can be null |
| * @return The dateTag |
| */ |
| private static String getMessageTag(String tag) { |
| Calendar c = Calendar.getInstance(); |
| |
| if (tag == null) { |
| return String.format("[%1$tF %1$tT]", c); |
| } |
| |
| return String.format("[%1$tF %1$tT - %2$s]", c, tag); |
| } |
| |
| |
| } |