[go: nahoru, domu]

blob: e746634f7aa99c48041379e92d2fb75c6bda2db1 [file] [log] [blame]
/*
* Copyright (C) 2010 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.hierarchyviewerlib;
import com.android.ddmlib.AndroidDebugBridge;
import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.hierarchyviewerlib.device.DeviceBridge;
import com.android.hierarchyviewerlib.device.HvDeviceFactory;
import com.android.hierarchyviewerlib.device.IHvDevice;
import com.android.hierarchyviewerlib.device.WindowUpdater;
import com.android.hierarchyviewerlib.device.WindowUpdater.IWindowChangeListener;
import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
import com.android.hierarchyviewerlib.models.PixelPerfectModel;
import com.android.hierarchyviewerlib.models.TreeViewModel;
import com.android.hierarchyviewerlib.models.ViewNode;
import com.android.hierarchyviewerlib.models.Window;
import com.android.hierarchyviewerlib.ui.CaptureDisplay;
import com.android.hierarchyviewerlib.ui.EvaluateContrastDisplay;
import com.android.hierarchyviewerlib.ui.TreeView;
import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
import com.android.hierarchyviewerlib.ui.util.PsdFile;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Shell;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
/**
* This is the class where most of the logic resides.
*/
public abstract class HierarchyViewerDirector implements IDeviceChangeListener,
IWindowChangeListener {
private static final boolean sIsUsingDdmProtocol;
static {
String sHvProtoEnvVar = System.getenv("ANDROID_HVPROTO"); //$NON-NLS-1$
sIsUsingDdmProtocol = "ddm".equalsIgnoreCase(sHvProtoEnvVar);
}
protected static HierarchyViewerDirector sDirector;
public static final String TAG = "hierarchyviewer";
private int mPixelPerfectRefreshesInProgress = 0;
private Timer mPixelPerfectRefreshTimer = new Timer();
private boolean mAutoRefresh = false;
public static final int DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL = 5;
private int mPixelPerfectAutoRefreshInterval = DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL;
private PixelPerfectAutoRefreshTask mCurrentAutoRefreshTask;
private String mFilterText = ""; //$NON-NLS-1$
private static final Object mDevicesLock = new Object();
private Map<IDevice, IHvDevice> mDevices = new HashMap<IDevice, IHvDevice>(10);
public static boolean isUsingDdmProtocol() {
return sIsUsingDdmProtocol;
}
public void terminate() {
WindowUpdater.terminate();
mPixelPerfectRefreshTimer.cancel();
}
public abstract String getAdbLocation();
public static HierarchyViewerDirector getDirector() {
return sDirector;
}
/**
* Init the DeviceBridge with an existing {@link AndroidDebugBridge}.
* @param bridge the bridge object to use
*/
public void acquireBridge(AndroidDebugBridge bridge) {
DeviceBridge.acquireBridge(bridge);
}
/**
* Creates an {@link AndroidDebugBridge} connected to adb at the given location.
*
* If a bridge is already running, this disconnects it and creates a new one.
*
* @param adbLocation the location to adb.
*/
public void initDebugBridge() {
DeviceBridge.initDebugBridge(getAdbLocation());
}
public void stopDebugBridge() {
DeviceBridge.terminate();
}
public void populateDeviceSelectionModel() {
IDevice[] devices = DeviceBridge.getDevices();
for (IDevice device : devices) {
deviceConnected(device);
}
}
public void startListenForDevices() {
DeviceBridge.startListenForDevices(this);
}
public void stopListenForDevices() {
DeviceBridge.stopListenForDevices(this);
}
public abstract void executeInBackground(String taskName, Runnable task);
@Override
public void deviceConnected(final IDevice device) {
executeInBackground("Connecting device", new Runnable() {
@Override
public void run() {
if (!device.isOnline()) {
return;
}
IHvDevice hvDevice;
synchronized (mDevicesLock) {
hvDevice = mDevices.get(device);
if (hvDevice == null) {
hvDevice = HvDeviceFactory.create(device);
hvDevice.initializeViewDebug();
hvDevice.addWindowChangeListener(getDirector());
mDevices.put(device, hvDevice);
} else {
// attempt re-initializing view server if device state has changed
hvDevice.initializeViewDebug();
}
}
DeviceSelectionModel.getModel().addDevice(hvDevice);
focusChanged(device);
}
});
}
@Override
public void deviceDisconnected(final IDevice device) {
executeInBackground("Disconnecting device", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice;
synchronized (mDevicesLock) {
hvDevice = mDevices.get(device);
if (hvDevice != null) {
mDevices.remove(device);
}
}
if (hvDevice == null) {
return;
}
hvDevice.terminateViewDebug();
hvDevice.removeWindowChangeListener(getDirector());
DeviceSelectionModel.getModel().removeDevice(hvDevice);
if (PixelPerfectModel.getModel().getDevice() == device) {
PixelPerfectModel.getModel().setData(null, null, null);
}
Window treeViewWindow = TreeViewModel.getModel().getWindow();
if (treeViewWindow != null && treeViewWindow.getDevice() == device) {
TreeViewModel.getModel().setData(null, null);
mFilterText = ""; //$NON-NLS-1$
}
}
});
}
@Override
public void windowsChanged(final IDevice device) {
executeInBackground("Refreshing windows", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(device);
hvDevice.reloadWindows();
DeviceSelectionModel.getModel().updateDevice(hvDevice);
}
});
}
@Override
public void focusChanged(final IDevice device) {
executeInBackground("Updating focus", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(device);
int focusedWindow = hvDevice.getFocusedWindow();
DeviceSelectionModel.getModel().updateFocusedWindow(hvDevice, focusedWindow);
}
});
}
@Override
public void deviceChanged(IDevice device, int changeMask) {
if ((changeMask & IDevice.CHANGE_STATE) != 0 && device.isOnline()) {
deviceConnected(device);
}
}
public void refreshPixelPerfect() {
final IDevice device = PixelPerfectModel.getModel().getDevice();
if (device != null) {
// Some interesting logic here. We don't want to refresh the pixel
// perfect view 1000 times in a row if the focus keeps changing. We
// just
// want it to refresh following the last focus change.
boolean proceed = false;
synchronized (this) {
if (mPixelPerfectRefreshesInProgress <= 1) {
proceed = true;
mPixelPerfectRefreshesInProgress++;
}
}
if (proceed) {
executeInBackground("Refreshing pixel perfect screenshot", new Runnable() {
@Override
public void run() {
Image screenshotImage = getScreenshotImage(getHvDevice(device));
if (screenshotImage != null) {
PixelPerfectModel.getModel().setImage(screenshotImage);
}
synchronized (HierarchyViewerDirector.this) {
mPixelPerfectRefreshesInProgress--;
}
}
});
}
}
}
public void refreshPixelPerfectTree() {
final IDevice device = PixelPerfectModel.getModel().getDevice();
if (device != null) {
executeInBackground("Refreshing pixel perfect tree", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(device);
ViewNode viewNode =
hvDevice.loadWindowData(Window.getFocusedWindow(hvDevice));
if (viewNode != null) {
PixelPerfectModel.getModel().setTree(viewNode);
}
}
});
}
}
public void loadPixelPerfectData(final IHvDevice hvDevice) {
executeInBackground("Loading pixel perfect data", new Runnable() {
@Override
public void run() {
Image screenshotImage = getScreenshotImage(hvDevice);
if (screenshotImage != null) {
ViewNode viewNode =
hvDevice.loadWindowData(Window.getFocusedWindow(hvDevice));
if (viewNode != null) {
PixelPerfectModel.getModel().setData(hvDevice.getDevice(),
screenshotImage, viewNode);
}
}
}
});
}
private IHvDevice getHvDevice(IDevice device) {
synchronized (mDevicesLock) {
return mDevices.get(device);
}
}
private Image getScreenshotImage(IHvDevice hvDevice) {
return (hvDevice == null) ? null : hvDevice.getScreenshotImage();
}
public void loadViewTreeData(final Window window) {
executeInBackground("Loading view hierarchy", new Runnable() {
@Override
public void run() {
mFilterText = ""; //$NON-NLS-1$
IHvDevice hvDevice = window.getHvDevice();
ViewNode viewNode = hvDevice.loadWindowData(window);
if (viewNode != null) {
viewNode.setViewCount();
TreeViewModel.getModel().setData(window, viewNode);
}
}
});
}
public void loadOverlay(final Shell shell) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
FileDialog fileDialog = new FileDialog(shell, SWT.OPEN);
fileDialog.setFilterExtensions(new String[] {
"*.jpg;*.jpeg;*.png;*.gif;*.bmp" //$NON-NLS-1$
});
fileDialog.setFilterNames(new String[] {
"Image (*.jpg, *.jpeg, *.png, *.gif, *.bmp)"
});
fileDialog.setText("Choose an overlay image");
String fileName = fileDialog.open();
if (fileName != null) {
try {
Image image = new Image(Display.getDefault(), fileName);
PixelPerfectModel.getModel().setOverlayImage(image);
} catch (SWTException e) {
Log.e(TAG, "Unable to load image from " + fileName);
}
}
}
});
}
public void showCapture(final Shell shell, final ViewNode viewNode) {
executeInBackground("Capturing node", new Runnable() {
@Override
public void run() {
final Image image = loadCapture(viewNode);
if (image != null) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
CaptureDisplay.show(shell, viewNode, image);
}
});
}
}
});
}
public void showEvaluateContrast(final Shell shell) {
executeInBackground("Capturing node and evaluating contrast", new Runnable() {
@Override
public void run() {
mFilterText = ""; //$NON-NLS-1$
Window window = TreeViewModel.getModel().getWindow();
IHvDevice hvDevice = window.getHvDevice();
final ViewNode viewNode = hvDevice.loadWindowData(window);
if (viewNode != null) {
viewNode.setViewCount();
TreeViewModel.getModel().setData(window, viewNode);
}
final Image image = loadCapture(viewNode);
if (image != null && viewNode != null) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
EvaluateContrastDisplay.show(shell, viewNode, image);
}
});
}
}
});
}
public Image loadCapture(ViewNode viewNode) {
IHvDevice hvDevice = viewNode.window.getHvDevice();
final Image image = hvDevice.loadCapture(viewNode.window, viewNode);
if (image != null) {
viewNode.image = image;
// Force the layout viewer to redraw.
TreeViewModel.getModel().notifySelectionChanged();
}
return image;
}
public void loadCaptureInBackground(final ViewNode viewNode) {
executeInBackground("Capturing node", new Runnable() {
@Override
public void run() {
loadCapture(viewNode);
}
});
}
public void showCapture(Shell shell) {
DrawableViewNode viewNode = TreeViewModel.getModel().getSelection();
if (viewNode != null) {
showCapture(shell, viewNode.viewNode);
}
}
public void refreshWindows() {
executeInBackground("Refreshing windows", new Runnable() {
@Override
public void run() {
IHvDevice[] hvDevicesA = DeviceSelectionModel.getModel().getDevices();
IDevice[] devicesA = new IDevice[hvDevicesA.length];
for (int i = 0; i < hvDevicesA.length; i++) {
devicesA[i] = hvDevicesA[i].getDevice();
}
IDevice[] devicesB = DeviceBridge.getDevices();
HashSet<IDevice> deviceSet = new HashSet<IDevice>();
for (int i = 0; i < devicesB.length; i++) {
deviceSet.add(devicesB[i]);
}
for (int i = 0; i < devicesA.length; i++) {
if (deviceSet.contains(devicesA[i])) {
windowsChanged(devicesA[i]);
deviceSet.remove(devicesA[i]);
} else {
deviceDisconnected(devicesA[i]);
}
}
for (IDevice device : deviceSet) {
deviceConnected(device);
}
}
});
}
public void loadViewHierarchy() {
Window window = DeviceSelectionModel.getModel().getSelectedWindow();
if (window != null) {
loadViewTreeData(window);
}
}
public void inspectScreenshot() {
IHvDevice device = DeviceSelectionModel.getModel().getSelectedDevice();
if (device != null) {
loadPixelPerfectData(device);
}
}
public void saveTreeView(final Shell shell) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
final DrawableViewNode viewNode = TreeViewModel.getModel().getTree();
if (viewNode != null) {
FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
fileDialog.setFilterExtensions(new String[] {
"*.png" //$NON-NLS-1$
});
fileDialog.setFilterNames(new String[] {
"Portable Network Graphics File (*.png)"
});
fileDialog.setText("Choose where to save the tree image");
final String fileName = fileDialog.open();
if (fileName != null) {
executeInBackground("Saving tree view", new Runnable() {
@Override
public void run() {
Image image = TreeView.paintToImage(viewNode);
ImageLoader imageLoader = new ImageLoader();
imageLoader.data = new ImageData[] {
image.getImageData()
};
String extensionedFileName = fileName;
if (!extensionedFileName.toLowerCase().endsWith(".png")) { //$NON-NLS-1$
extensionedFileName += ".png"; //$NON-NLS-1$
}
try {
imageLoader.save(extensionedFileName, SWT.IMAGE_PNG);
} catch (SWTException e) {
Log.e(TAG, "Unable to save tree view as a PNG image at "
+ fileName);
}
image.dispose();
}
});
}
}
}
});
}
public void savePixelPerfect(final Shell shell) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
Image untouchableImage = PixelPerfectModel.getModel().getImage();
if (untouchableImage != null) {
final ImageData imageData = untouchableImage.getImageData();
FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
fileDialog.setFilterExtensions(new String[] {
"*.png" //$NON-NLS-1$
});
fileDialog.setFilterNames(new String[] {
"Portable Network Graphics File (*.png)"
});
fileDialog.setText("Choose where to save the screenshot");
final String fileName = fileDialog.open();
if (fileName != null) {
executeInBackground("Saving pixel perfect", new Runnable() {
@Override
public void run() {
ImageLoader imageLoader = new ImageLoader();
imageLoader.data = new ImageData[] {
imageData
};
String extensionedFileName = fileName;
if (!extensionedFileName.toLowerCase().endsWith(".png")) { //$NON-NLS-1$
extensionedFileName += ".png"; //$NON-NLS-1$
}
try {
imageLoader.save(extensionedFileName, SWT.IMAGE_PNG);
} catch (SWTException e) {
Log.e(TAG, "Unable to save tree view as a PNG image at "
+ fileName);
}
}
});
}
}
}
});
}
public void capturePSD(final Shell shell) {
Display.getDefault().syncExec(new Runnable() {
@Override
public void run() {
final Window window = TreeViewModel.getModel().getWindow();
if (window != null) {
FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
fileDialog.setFilterExtensions(new String[] {
"*.psd" //$NON-NLS-1$
});
fileDialog.setFilterNames(new String[] {
"Photoshop Document (*.psd)"
});
fileDialog.setText("Choose where to save the window layers");
final String fileName = fileDialog.open();
if (fileName != null) {
executeInBackground("Saving window layers", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(window.getDevice());
PsdFile psdFile = hvDevice.captureLayers(window);
if (psdFile != null) {
String extensionedFileName = fileName;
if (!extensionedFileName.toLowerCase().endsWith(".psd")) { //$NON-NLS-1$
extensionedFileName += ".psd"; //$NON-NLS-1$
}
try {
psdFile.write(new FileOutputStream(extensionedFileName));
} catch (FileNotFoundException e) {
Log.e(TAG, "Unable to write to file " + fileName);
}
}
}
});
}
}
}
});
}
public void reloadViewHierarchy() {
Window window = TreeViewModel.getModel().getWindow();
if (window != null) {
loadViewTreeData(window);
}
}
public void invalidateCurrentNode() {
final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
if (selectedNode != null) {
executeInBackground("Invalidating view", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(selectedNode.viewNode.window.getDevice());
hvDevice.invalidateView(selectedNode.viewNode);
}
});
}
}
public void relayoutCurrentNode() {
final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
if (selectedNode != null) {
executeInBackground("Request layout", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(selectedNode.viewNode.window.getDevice());
hvDevice.requestLayout(selectedNode.viewNode);
}
});
}
}
public void dumpDisplayListForCurrentNode() {
final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
if (selectedNode != null) {
executeInBackground("Dump displaylist", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(selectedNode.viewNode.window.getDevice());
hvDevice.outputDisplayList(selectedNode.viewNode);
}
});
}
}
public void profileCurrentNode() {
final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
if (selectedNode != null) {
executeInBackground("Profile Node", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(selectedNode.viewNode.window.getDevice());
hvDevice.loadProfileData(selectedNode.viewNode.window, selectedNode.viewNode);
// Force the layout viewer to redraw.
TreeViewModel.getModel().notifySelectionChanged();
}
});
}
}
public void invokeMethodOnSelectedView(final String method, final List<Object> args) {
final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
if (selectedNode != null) {
executeInBackground("Invoke View Method", new Runnable() {
@Override
public void run() {
IHvDevice hvDevice = getHvDevice(selectedNode.viewNode.window.getDevice());
hvDevice.invokeViewMethod(selectedNode.viewNode.window, selectedNode.viewNode,
method, args);
}
});
}
}
public void loadAllViews() {
executeInBackground("Loading all views", new Runnable() {
@Override
public void run() {
DrawableViewNode tree = TreeViewModel.getModel().getTree();
if (tree != null) {
loadViewRecursive(tree.viewNode);
// Force the layout viewer to redraw.
TreeViewModel.getModel().notifySelectionChanged();
}
}
});
}
private void loadViewRecursive(ViewNode viewNode) {
IHvDevice hvDevice = getHvDevice(viewNode.window.getDevice());
Image image = hvDevice.loadCapture(viewNode.window, viewNode);
if (image == null) {
return;
}
viewNode.image = image;
final int N = viewNode.children.size();
for (int i = 0; i < N; i++) {
loadViewRecursive(viewNode.children.get(i));
}
}
public void filterNodes(String filterText) {
this.mFilterText = filterText;
DrawableViewNode tree = TreeViewModel.getModel().getTree();
if (tree != null) {
tree.viewNode.filter(filterText);
// Force redraw
TreeViewModel.getModel().notifySelectionChanged();
}
}
public String getFilterText() {
return mFilterText;
}
private static class PixelPerfectAutoRefreshTask extends TimerTask {
@Override
public void run() {
HierarchyViewerDirector.getDirector().refreshPixelPerfect();
}
};
public void setPixelPerfectAutoRefresh(boolean value) {
synchronized (mPixelPerfectRefreshTimer) {
if (value == mAutoRefresh) {
return;
}
mAutoRefresh = value;
if (mAutoRefresh) {
mCurrentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
mPixelPerfectRefreshTimer.schedule(mCurrentAutoRefreshTask,
mPixelPerfectAutoRefreshInterval * 1000,
mPixelPerfectAutoRefreshInterval * 1000);
} else {
mCurrentAutoRefreshTask.cancel();
mCurrentAutoRefreshTask = null;
}
}
}
public void setPixelPerfectAutoRefreshInterval(int value) {
synchronized (mPixelPerfectRefreshTimer) {
if (mPixelPerfectAutoRefreshInterval == value) {
return;
}
mPixelPerfectAutoRefreshInterval = value;
if (mAutoRefresh) {
mCurrentAutoRefreshTask.cancel();
long timeLeft =
Math.max(0, mPixelPerfectAutoRefreshInterval
* 1000
- (System.currentTimeMillis() - mCurrentAutoRefreshTask
.scheduledExecutionTime()));
mCurrentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
mPixelPerfectRefreshTimer.schedule(mCurrentAutoRefreshTask, timeLeft,
mPixelPerfectAutoRefreshInterval * 1000);
}
}
}
public int getPixelPerfectAutoRefreshInverval() {
return mPixelPerfectAutoRefreshInterval;
}
}