/*
* Copyright (C) 2013 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.tools.idea.ddms;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.Client;
import com.android.ddmlib.ClientData;
import com.android.ddmlib.IDevice;
import com.android.tools.idea.ddms.actions.*;
import com.android.tools.idea.ddms.hprof.DumpHprofAction;
import com.android.tools.idea.ddms.hprof.SaveHprofHandler;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Separator;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.ListSpeedSearch;
import com.intellij.ui.SortedListModel;
import com.intellij.ui.components.JBList;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class DevicePanel implements Disposable,
AndroidDebugBridge.IDeviceChangeListener,
AndroidDebugBridge.IDebugBridgeChangeListener {
private static final String NO_DEVICES = "No Connected Devices";
private JPanel myPanel;
private JComboBox myDevicesComboBox;
private JBList myClientsList;
private final DefaultComboBoxModel myComboBoxModel = new DefaultComboBoxModel();
private final SortedListModel<Client> myClientsListModel = new SortedListModel<Client>(new ClientCellRenderer.ClientComparator());
private boolean myIgnoreListeners;
private final DeviceContext myDeviceContext;
private final Project myProject;
@Nullable private AndroidDebugBridge myBridge;
public DevicePanel(@NotNull Project project, @NotNull DeviceContext context) {
myProject = project;
myDeviceContext = context;
Disposer.register(myProject, this);
AndroidDebugBridge.addDeviceChangeListener(this);
AndroidDebugBridge.addDebugBridgeChangeListener(this);
ClientData.setMethodProfilingHandler(new OpenVmTraceHandler(project));
ClientData.setHprofDumpHandler(new SaveHprofHandler(project));
ClientData.setAllocationTrackingHandler(new ShowAllocationsHandler(project));
initializeDeviceCombo();
initializeClientsList();
}
private void initializeDeviceCombo() {
myDevicesComboBox.setModel(myComboBoxModel);
myDevicesComboBox.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
Object sel = myDevicesComboBox.getSelectedItem();
IDevice device = (sel instanceof IDevice) ? (IDevice)sel : null;
updateClientsForDevice(device);
myDeviceContext.fireDeviceSelected(device);
myDeviceContext.fireClientSelected(null);
}
});
myDevicesComboBox.setRenderer(new DeviceRenderer.DeviceComboBoxRenderer());
}
private void setBridge(@Nullable AndroidDebugBridge bridge) {
myBridge = bridge;
myComboBoxModel.removeAllElements();
IDevice[] devices = myBridge == null ? null : myBridge.getDevices();
if (devices == null || devices.length == 0) {
myComboBoxModel.addElement(NO_DEVICES);
} else {
for (IDevice device : devices) {
myComboBoxModel.addElement(device);
}
}
myDevicesComboBox.setSelectedIndex(0);
}
public void selectDevice(@NotNull final IDevice device) {
myDevicesComboBox.setSelectedItem(device);
}
private void initializeClientsList() {
myClientsList.setModel(myClientsListModel);
myClientsList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
myClientsList.setEmptyText("No debuggable applications");
myClientsList.setCellRenderer(new ClientCellRenderer());
new ListSpeedSearch(myClientsList) {
@Override
protected boolean isMatchingElement(Object element, String pattern) {
if (element instanceof Client) {
String pkg = ((Client)element).getClientData().getClientDescription();
return pkg != null && pkg.contains(pattern);
}
return false;
}
};
myClientsList.addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() || myIgnoreListeners) {
return;
}
Object sel = myClientsList.getSelectedValue();
Client c = (sel instanceof Client) ? (Client)sel : null;
myDeviceContext.fireClientSelected(c);
}
});
}
@Override
public void dispose() {
if (myBridge != null) {
AndroidDebugBridge.removeDeviceChangeListener(this);
AndroidDebugBridge.removeDebugBridgeChangeListener(this);
myBridge = null;
}
}
public JPanel getContentPanel() {
return myPanel;
}
@Override
public void bridgeChanged(final AndroidDebugBridge bridge) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
setBridge(bridge);
}
});
}
@Override
public void deviceConnected(final IDevice device) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myComboBoxModel.removeElement(NO_DEVICES);
myComboBoxModel.addElement(device);
}
});
}
@Override
public void deviceDisconnected(final IDevice device) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myComboBoxModel.removeElement(device);
if (myComboBoxModel.getSize() == 0) {
myComboBoxModel.addElement(NO_DEVICES);
}
}
});
}
@Override
public void deviceChanged(final IDevice device, final int changeMask) {
UIUtil.invokeLaterIfNeeded(new Runnable() {
@Override
public void run() {
myDevicesComboBox.repaint();
if (device != myDevicesComboBox.getSelectedItem()) {
return;
}
if ((changeMask & IDevice.CHANGE_CLIENT_LIST) == IDevice.CHANGE_CLIENT_LIST) {
updateClientsForDevice(device);
}
if (device != null) {
myDeviceContext.fireDeviceChanged(device, changeMask);
}
}
});
}
private void updateClientsForDevice(@Nullable IDevice device) {
if (device == null) {
// Note: we do want listeners triggered when the device itself disappears.
// so we don't set myIgnoreListeners
myClientsListModel.clear();
return;
}
Object selectedObject = myClientsList.getSelectedValue();
Client[] clients = device.getClients();
try {
// we want to refresh the list of clients, however we don't want the listeners to
// think that this is a user driven change to the list selection.
// the only time this update should trigger the selection listener is if the currently selected client isn't there anymore
myIgnoreListeners = ArrayUtil.contains(selectedObject, clients);
myClientsListModel.clear();
myClientsListModel.addAll(clients);
myClientsList.setSelectedValue(selectedObject, true);
} finally {
myIgnoreListeners = false;
}
}
@NotNull
public ActionGroup getToolbarActions() {
DefaultActionGroup group = new DefaultActionGroup();
group.add(new ScreenshotAction(myProject, myDeviceContext));
group.add(new ScreenRecorderAction(myProject, myDeviceContext));
group.add(DumpSysActions.create(myProject, myDeviceContext));
//group.add(new MyFileExplorerAction());
group.add(new Separator());
group.add(new TerminateVMAction(myDeviceContext));
group.add(new GcAction(myDeviceContext));
group.add(new DumpHprofAction(myDeviceContext));
//group.add(new MyAllocationTrackerAction());
//group.add(new Separator());
group.add(new ToggleMethodProfilingAction(myProject, myDeviceContext));
//group.add(new MyThreadDumpAction()); // thread dump -> systrace
group.add(new ToggleAllocationTrackingAction(myDeviceContext));
return group;
}
}