• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 
17 package com.android.hierarchyviewerlib;
18 
19 import com.android.ddmlib.AdbCommandRejectedException;
20 import com.android.ddmlib.AndroidDebugBridge;
21 import com.android.ddmlib.AndroidDebugBridge.IDeviceChangeListener;
22 import com.android.ddmlib.IDevice;
23 import com.android.ddmlib.Log;
24 import com.android.ddmlib.RawImage;
25 import com.android.ddmlib.TimeoutException;
26 import com.android.hierarchyviewerlib.device.DeviceBridge;
27 import com.android.hierarchyviewerlib.device.DeviceBridge.ViewServerInfo;
28 import com.android.hierarchyviewerlib.device.ViewNode;
29 import com.android.hierarchyviewerlib.device.Window;
30 import com.android.hierarchyviewerlib.device.WindowUpdater;
31 import com.android.hierarchyviewerlib.device.WindowUpdater.IWindowChangeListener;
32 import com.android.hierarchyviewerlib.models.DeviceSelectionModel;
33 import com.android.hierarchyviewerlib.models.PixelPerfectModel;
34 import com.android.hierarchyviewerlib.models.TreeViewModel;
35 import com.android.hierarchyviewerlib.ui.CaptureDisplay;
36 import com.android.hierarchyviewerlib.ui.TreeView;
37 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
38 import com.android.hierarchyviewerlib.ui.util.PsdFile;
39 
40 import org.eclipse.swt.SWT;
41 import org.eclipse.swt.SWTException;
42 import org.eclipse.swt.graphics.Image;
43 import org.eclipse.swt.graphics.ImageData;
44 import org.eclipse.swt.graphics.ImageLoader;
45 import org.eclipse.swt.graphics.PaletteData;
46 import org.eclipse.swt.widgets.Display;
47 import org.eclipse.swt.widgets.FileDialog;
48 import org.eclipse.swt.widgets.Shell;
49 
50 import java.io.FileNotFoundException;
51 import java.io.FileOutputStream;
52 import java.io.IOException;
53 import java.util.HashSet;
54 import java.util.Timer;
55 import java.util.TimerTask;
56 
57 /**
58  * This is the class where most of the logic resides.
59  */
60 public abstract class HierarchyViewerDirector implements IDeviceChangeListener,
61         IWindowChangeListener {
62 
63     protected static HierarchyViewerDirector sDirector;
64 
65     public static final String TAG = "hierarchyviewer";
66 
67     private int mPixelPerfectRefreshesInProgress = 0;
68 
69     private Timer mPixelPerfectRefreshTimer = new Timer();
70 
71     private boolean mAutoRefresh = false;
72 
73     public static final int DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL = 5;
74 
75     private int mPixelPerfectAutoRefreshInterval = DEFAULT_PIXEL_PERFECT_AUTOREFRESH_INTERVAL;
76 
77     private PixelPerfectAutoRefreshTask mCurrentAutoRefreshTask;
78 
79     private String mFilterText = ""; //$NON-NLS-1$
80 
terminate()81     public void terminate() {
82         WindowUpdater.terminate();
83         mPixelPerfectRefreshTimer.cancel();
84     }
85 
getAdbLocation()86     public abstract String getAdbLocation();
87 
getDirector()88     public static HierarchyViewerDirector getDirector() {
89         return sDirector;
90     }
91 
92     /**
93      * Init the DeviceBridge with an existing {@link AndroidDebugBridge}.
94      * @param bridge the bridge object to use
95      */
acquireBridge(AndroidDebugBridge bridge)96     public void acquireBridge(AndroidDebugBridge bridge) {
97         DeviceBridge.acquireBridge(bridge);
98     }
99 
100     /**
101      * Creates an {@link AndroidDebugBridge} connected to adb at the given location.
102      *
103      * If a bridge is already running, this disconnects it and creates a new one.
104      *
105      * @param adbLocation the location to adb.
106      */
initDebugBridge()107     public void initDebugBridge() {
108         DeviceBridge.initDebugBridge(getAdbLocation());
109     }
110 
stopDebugBridge()111     public void stopDebugBridge() {
112         DeviceBridge.terminate();
113     }
114 
populateDeviceSelectionModel()115     public void populateDeviceSelectionModel() {
116         IDevice[] devices = DeviceBridge.getDevices();
117         for (IDevice device : devices) {
118             deviceConnected(device);
119         }
120     }
121 
startListenForDevices()122     public void startListenForDevices() {
123         DeviceBridge.startListenForDevices(this);
124     }
125 
stopListenForDevices()126     public void stopListenForDevices() {
127         DeviceBridge.stopListenForDevices(this);
128     }
129 
executeInBackground(String taskName, Runnable task)130     public abstract void executeInBackground(String taskName, Runnable task);
131 
132     @Override
deviceConnected(final IDevice device)133     public void deviceConnected(final IDevice device) {
134         executeInBackground("Connecting device", new Runnable() {
135             @Override
136             public void run() {
137                 if (DeviceSelectionModel.getModel().containsDevice(device)) {
138                     windowsChanged(device);
139                 } else if (device.isOnline()) {
140                     DeviceBridge.setupDeviceForward(device);
141                     if (!DeviceBridge.isViewServerRunning(device)) {
142                         if (!DeviceBridge.startViewServer(device)) {
143                             // Let's do something interesting here... Try again
144                             // in 2 seconds.
145                             try {
146                                 Thread.sleep(2000);
147                             } catch (InterruptedException e) {
148                             }
149                             if (!DeviceBridge.startViewServer(device)) {
150                                 Log.e(TAG, "Unable to debug device " + device);
151                                 DeviceBridge.removeDeviceForward(device);
152                             } else {
153                                 loadViewServerInfoAndWindows(device);
154                             }
155                             return;
156                         }
157                     }
158                     loadViewServerInfoAndWindows(device);
159                 }
160             }
161         });
162     }
163 
loadViewServerInfoAndWindows(final IDevice device)164     private void loadViewServerInfoAndWindows(final IDevice device) {
165 
166         ViewServerInfo viewServerInfo = DeviceBridge.loadViewServerInfo(device);
167         if (viewServerInfo == null) {
168             return;
169         }
170         Window[] windows = DeviceBridge.loadWindows(device);
171         DeviceSelectionModel.getModel().addDevice(device, windows, viewServerInfo);
172         if (viewServerInfo.protocolVersion >= 3) {
173             WindowUpdater.startListenForWindowChanges(HierarchyViewerDirector.this, device);
174             focusChanged(device);
175         }
176 
177     }
178 
179     @Override
deviceDisconnected(final IDevice device)180     public void deviceDisconnected(final IDevice device) {
181         executeInBackground("Disconnecting device", new Runnable() {
182             @Override
183             public void run() {
184                 ViewServerInfo viewServerInfo = DeviceBridge.getViewServerInfo(device);
185                 if (viewServerInfo != null && viewServerInfo.protocolVersion >= 3) {
186                     WindowUpdater.stopListenForWindowChanges(HierarchyViewerDirector.this, device);
187                 }
188                 DeviceBridge.removeDeviceForward(device);
189                 DeviceBridge.removeViewServerInfo(device);
190                 DeviceSelectionModel.getModel().removeDevice(device);
191                 if (PixelPerfectModel.getModel().getDevice() == device) {
192                     PixelPerfectModel.getModel().setData(null, null, null);
193                 }
194                 Window treeViewWindow = TreeViewModel.getModel().getWindow();
195                 if (treeViewWindow != null && treeViewWindow.getDevice() == device) {
196                     TreeViewModel.getModel().setData(null, null);
197                     mFilterText = ""; //$NON-NLS-1$
198                 }
199             }
200         });
201     }
202 
203     @Override
deviceChanged(IDevice device, int changeMask)204     public void deviceChanged(IDevice device, int changeMask) {
205         if ((changeMask & IDevice.CHANGE_STATE) != 0 && device.isOnline()) {
206             deviceConnected(device);
207         }
208     }
209 
210     @Override
windowsChanged(final IDevice device)211     public void windowsChanged(final IDevice device) {
212         executeInBackground("Refreshing windows", new Runnable() {
213             @Override
214             public void run() {
215                 if (!DeviceBridge.isViewServerRunning(device)) {
216                     if (!DeviceBridge.startViewServer(device)) {
217                         Log.e(TAG, "Unable to debug device " + device);
218                         return;
219                     }
220                 }
221                 Window[] windows = DeviceBridge.loadWindows(device);
222                 DeviceSelectionModel.getModel().updateDevice(device, windows);
223             }
224         });
225     }
226 
227     @Override
focusChanged(final IDevice device)228     public void focusChanged(final IDevice device) {
229         executeInBackground("Updating focus", new Runnable() {
230             @Override
231             public void run() {
232                 int focusedWindow = DeviceBridge.getFocusedWindow(device);
233                 DeviceSelectionModel.getModel().updateFocusedWindow(device, focusedWindow);
234             }
235         });
236     }
237 
refreshPixelPerfect()238     public void refreshPixelPerfect() {
239         final IDevice device = PixelPerfectModel.getModel().getDevice();
240         if (device != null) {
241             // Some interesting logic here. We don't want to refresh the pixel
242             // perfect view 1000 times in a row if the focus keeps changing. We
243             // just
244             // want it to refresh following the last focus change.
245             boolean proceed = false;
246             synchronized (this) {
247                 if (mPixelPerfectRefreshesInProgress <= 1) {
248                     proceed = true;
249                     mPixelPerfectRefreshesInProgress++;
250                 }
251             }
252             if (proceed) {
253                 executeInBackground("Refreshing pixel perfect screenshot", new Runnable() {
254                     @Override
255                     public void run() {
256                         Image screenshotImage = getScreenshotImage(device);
257                         if (screenshotImage != null) {
258                             PixelPerfectModel.getModel().setImage(screenshotImage);
259                         }
260                         synchronized (HierarchyViewerDirector.this) {
261                             mPixelPerfectRefreshesInProgress--;
262                         }
263                     }
264 
265                 });
266             }
267         }
268     }
269 
refreshPixelPerfectTree()270     public void refreshPixelPerfectTree() {
271         final IDevice device = PixelPerfectModel.getModel().getDevice();
272         if (device != null) {
273             executeInBackground("Refreshing pixel perfect tree", new Runnable() {
274                 @Override
275                 public void run() {
276                     ViewNode viewNode =
277                             DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
278                     if (viewNode != null) {
279                         PixelPerfectModel.getModel().setTree(viewNode);
280                     }
281                 }
282 
283             });
284         }
285     }
286 
loadPixelPerfectData(final IDevice device)287     public void loadPixelPerfectData(final IDevice device) {
288         executeInBackground("Loading pixel perfect data", new Runnable() {
289             @Override
290             public void run() {
291                 Image screenshotImage = getScreenshotImage(device);
292                 if (screenshotImage != null) {
293                     ViewNode viewNode =
294                             DeviceBridge.loadWindowData(Window.getFocusedWindow(device));
295                     if (viewNode != null) {
296                         PixelPerfectModel.getModel().setData(device, screenshotImage, viewNode);
297                     }
298                 }
299             }
300         });
301     }
302 
getScreenshotImage(IDevice device)303     private Image getScreenshotImage(IDevice device) {
304         try {
305             final RawImage screenshot = device.getScreenshot();
306             if (screenshot == null) {
307                 return null;
308             }
309             class ImageContainer {
310                 public Image image;
311             }
312             final ImageContainer imageContainer = new ImageContainer();
313             Display.getDefault().syncExec(new Runnable() {
314                 @Override
315                 public void run() {
316                     ImageData imageData =
317                             new ImageData(screenshot.width, screenshot.height, screenshot.bpp,
318                                     new PaletteData(screenshot.getRedMask(), screenshot
319                                             .getGreenMask(), screenshot.getBlueMask()), 1,
320                                     screenshot.data);
321                     imageContainer.image = new Image(Display.getDefault(), imageData);
322                 }
323             });
324             return imageContainer.image;
325         } catch (IOException e) {
326             Log.e(TAG, "Unable to load screenshot from device " + device);
327         } catch (TimeoutException e) {
328             Log.e(TAG, "Timeout loading screenshot from device " + device);
329         } catch (AdbCommandRejectedException e) {
330             Log.e(TAG, "Adb rejected command to load screenshot from device " + device);
331         }
332         return null;
333     }
334 
loadViewTreeData(final Window window)335     public void loadViewTreeData(final Window window) {
336         executeInBackground("Loading view hierarchy", new Runnable() {
337             @Override
338             public void run() {
339 
340                 mFilterText = ""; //$NON-NLS-1$
341 
342                 ViewNode viewNode = DeviceBridge.loadWindowData(window);
343                 if (viewNode != null) {
344                     DeviceBridge.loadProfileData(window, viewNode);
345                     viewNode.setViewCount();
346                     TreeViewModel.getModel().setData(window, viewNode);
347                 }
348             }
349         });
350     }
351 
loadOverlay(final Shell shell)352     public void loadOverlay(final Shell shell) {
353         Display.getDefault().syncExec(new Runnable() {
354             @Override
355             public void run() {
356                 FileDialog fileDialog = new FileDialog(shell, SWT.OPEN);
357                 fileDialog.setFilterExtensions(new String[] {
358                     "*.jpg;*.jpeg;*.png;*.gif;*.bmp" //$NON-NLS-1$
359                 });
360                 fileDialog.setFilterNames(new String[] {
361                     "Image (*.jpg, *.jpeg, *.png, *.gif, *.bmp)"
362                 });
363                 fileDialog.setText("Choose an overlay image");
364                 String fileName = fileDialog.open();
365                 if (fileName != null) {
366                     try {
367                         Image image = new Image(Display.getDefault(), fileName);
368                         PixelPerfectModel.getModel().setOverlayImage(image);
369                     } catch (SWTException e) {
370                         Log.e(TAG, "Unable to load image from " + fileName);
371                     }
372                 }
373             }
374         });
375     }
376 
showCapture(final Shell shell, final ViewNode viewNode)377     public void showCapture(final Shell shell, final ViewNode viewNode) {
378         executeInBackground("Capturing node", new Runnable() {
379             @Override
380             public void run() {
381                 final Image image = loadCapture(viewNode);
382                 if (image != null) {
383 
384                     Display.getDefault().syncExec(new Runnable() {
385                         @Override
386                         public void run() {
387                             CaptureDisplay.show(shell, viewNode, image);
388                         }
389                     });
390                 }
391             }
392         });
393     }
394 
loadCapture(ViewNode viewNode)395     public Image loadCapture(ViewNode viewNode) {
396         final Image image = DeviceBridge.loadCapture(viewNode.window, viewNode);
397         if (image != null) {
398             viewNode.image = image;
399 
400             // Force the layout viewer to redraw.
401             TreeViewModel.getModel().notifySelectionChanged();
402         }
403         return image;
404     }
405 
loadCaptureInBackground(final ViewNode viewNode)406     public void loadCaptureInBackground(final ViewNode viewNode) {
407         executeInBackground("Capturing node", new Runnable() {
408             @Override
409             public void run() {
410                 loadCapture(viewNode);
411             }
412         });
413     }
414 
showCapture(Shell shell)415     public void showCapture(Shell shell) {
416         DrawableViewNode viewNode = TreeViewModel.getModel().getSelection();
417         if (viewNode != null) {
418             showCapture(shell, viewNode.viewNode);
419         }
420     }
421 
refreshWindows()422     public void refreshWindows() {
423         executeInBackground("Refreshing windows", new Runnable() {
424             @Override
425             public void run() {
426                 IDevice[] devicesA = DeviceSelectionModel.getModel().getDevices();
427                 IDevice[] devicesB = DeviceBridge.getDevices();
428                 HashSet<IDevice> deviceSet = new HashSet<IDevice>();
429                 for (int i = 0; i < devicesB.length; i++) {
430                     deviceSet.add(devicesB[i]);
431                 }
432                 for (int i = 0; i < devicesA.length; i++) {
433                     if (deviceSet.contains(devicesA[i])) {
434                         windowsChanged(devicesA[i]);
435                         deviceSet.remove(devicesA[i]);
436                     } else {
437                         deviceDisconnected(devicesA[i]);
438                     }
439                 }
440                 for (IDevice device : deviceSet) {
441                     deviceConnected(device);
442                 }
443             }
444         });
445     }
446 
loadViewHierarchy()447     public void loadViewHierarchy() {
448         Window window = DeviceSelectionModel.getModel().getSelectedWindow();
449         if (window != null) {
450             loadViewTreeData(window);
451         }
452     }
453 
inspectScreenshot()454     public void inspectScreenshot() {
455         IDevice device = DeviceSelectionModel.getModel().getSelectedDevice();
456         if (device != null) {
457             loadPixelPerfectData(device);
458         }
459     }
460 
saveTreeView(final Shell shell)461     public void saveTreeView(final Shell shell) {
462         Display.getDefault().syncExec(new Runnable() {
463             @Override
464             public void run() {
465                 final DrawableViewNode viewNode = TreeViewModel.getModel().getTree();
466                 if (viewNode != null) {
467                     FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
468                     fileDialog.setFilterExtensions(new String[] {
469                         "*.png" //$NON-NLS-1$
470                     });
471                     fileDialog.setFilterNames(new String[] {
472                         "Portable Network Graphics File (*.png)"
473                     });
474                     fileDialog.setText("Choose where to save the tree image");
475                     final String fileName = fileDialog.open();
476                     if (fileName != null) {
477                         executeInBackground("Saving tree view", new Runnable() {
478                             @Override
479                             public void run() {
480                                 Image image = TreeView.paintToImage(viewNode);
481                                 ImageLoader imageLoader = new ImageLoader();
482                                 imageLoader.data = new ImageData[] {
483                                     image.getImageData()
484                                 };
485                                 String extensionedFileName = fileName;
486                                 if (!extensionedFileName.toLowerCase().endsWith(".png")) { //$NON-NLS-1$
487                                     extensionedFileName += ".png"; //$NON-NLS-1$
488                                 }
489                                 try {
490                                     imageLoader.save(extensionedFileName, SWT.IMAGE_PNG);
491                                 } catch (SWTException e) {
492                                     Log.e(TAG, "Unable to save tree view as a PNG image at "
493                                             + fileName);
494                                 }
495                                 image.dispose();
496                             }
497                         });
498                     }
499                 }
500             }
501         });
502     }
503 
savePixelPerfect(final Shell shell)504     public void savePixelPerfect(final Shell shell) {
505         Display.getDefault().syncExec(new Runnable() {
506             @Override
507             public void run() {
508                 Image untouchableImage = PixelPerfectModel.getModel().getImage();
509                 if (untouchableImage != null) {
510                     final ImageData imageData = untouchableImage.getImageData();
511                     FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
512                     fileDialog.setFilterExtensions(new String[] {
513                         "*.png" //$NON-NLS-1$
514                     });
515                     fileDialog.setFilterNames(new String[] {
516                         "Portable Network Graphics File (*.png)"
517                     });
518                     fileDialog.setText("Choose where to save the screenshot");
519                     final String fileName = fileDialog.open();
520                     if (fileName != null) {
521                         executeInBackground("Saving pixel perfect", new Runnable() {
522                             @Override
523                             public void run() {
524                                 ImageLoader imageLoader = new ImageLoader();
525                                 imageLoader.data = new ImageData[] {
526                                     imageData
527                                 };
528                                 String extensionedFileName = fileName;
529                                 if (!extensionedFileName.toLowerCase().endsWith(".png")) { //$NON-NLS-1$
530                                     extensionedFileName += ".png"; //$NON-NLS-1$
531                                 }
532                                 try {
533                                     imageLoader.save(extensionedFileName, SWT.IMAGE_PNG);
534                                 } catch (SWTException e) {
535                                     Log.e(TAG, "Unable to save tree view as a PNG image at "
536                                             + fileName);
537                                 }
538                             }
539                         });
540                     }
541                 }
542             }
543         });
544     }
545 
capturePSD(final Shell shell)546     public void capturePSD(final Shell shell) {
547         Display.getDefault().syncExec(new Runnable() {
548             @Override
549             public void run() {
550                 final Window window = TreeViewModel.getModel().getWindow();
551                 if (window != null) {
552                     FileDialog fileDialog = new FileDialog(shell, SWT.SAVE);
553                     fileDialog.setFilterExtensions(new String[] {
554                         "*.psd" //$NON-NLS-1$
555                     });
556                     fileDialog.setFilterNames(new String[] {
557                         "Photoshop Document (*.psd)"
558                     });
559                     fileDialog.setText("Choose where to save the window layers");
560                     final String fileName = fileDialog.open();
561                     if (fileName != null) {
562                         executeInBackground("Saving window layers", new Runnable() {
563                             @Override
564                             public void run() {
565                                 PsdFile psdFile = DeviceBridge.captureLayers(window);
566                                 if (psdFile != null) {
567                                     String extensionedFileName = fileName;
568                                     if (!extensionedFileName.toLowerCase().endsWith(".psd")) { //$NON-NLS-1$
569                                         extensionedFileName += ".psd"; //$NON-NLS-1$
570                                     }
571                                     try {
572                                         psdFile.write(new FileOutputStream(extensionedFileName));
573                                     } catch (FileNotFoundException e) {
574                                         Log.e(TAG, "Unable to write to file " + fileName);
575                                     }
576                                 }
577                             }
578                         });
579                     }
580                 }
581             }
582         });
583     }
584 
reloadViewHierarchy()585     public void reloadViewHierarchy() {
586         Window window = TreeViewModel.getModel().getWindow();
587         if (window != null) {
588             loadViewTreeData(window);
589         }
590     }
591 
invalidateCurrentNode()592     public void invalidateCurrentNode() {
593         final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
594         if (selectedNode != null) {
595             executeInBackground("Invalidating view", new Runnable() {
596                 @Override
597                 public void run() {
598                     DeviceBridge.invalidateView(selectedNode.viewNode);
599                 }
600             });
601         }
602     }
603 
relayoutCurrentNode()604     public void relayoutCurrentNode() {
605         final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
606         if (selectedNode != null) {
607             executeInBackground("Request layout", new Runnable() {
608                 @Override
609                 public void run() {
610                     DeviceBridge.requestLayout(selectedNode.viewNode);
611                 }
612             });
613         }
614     }
615 
dumpDisplayListForCurrentNode()616     public void dumpDisplayListForCurrentNode() {
617         final DrawableViewNode selectedNode = TreeViewModel.getModel().getSelection();
618         if (selectedNode != null) {
619             executeInBackground("Dump displaylist", new Runnable() {
620                 @Override
621                 public void run() {
622                     DeviceBridge.outputDisplayList(selectedNode.viewNode);
623                 }
624             });
625         }
626     }
627 
loadAllViews()628     public void loadAllViews() {
629         executeInBackground("Loading all views", new Runnable() {
630             @Override
631             public void run() {
632                 DrawableViewNode tree = TreeViewModel.getModel().getTree();
633                 if (tree != null) {
634                     loadViewRecursive(tree.viewNode);
635                     // Force the layout viewer to redraw.
636                     TreeViewModel.getModel().notifySelectionChanged();
637                 }
638             }
639         });
640     }
641 
loadViewRecursive(ViewNode viewNode)642     private void loadViewRecursive(ViewNode viewNode) {
643         Image image = DeviceBridge.loadCapture(viewNode.window, viewNode);
644         if (image == null) {
645             return;
646         }
647         viewNode.image = image;
648         final int N = viewNode.children.size();
649         for (int i = 0; i < N; i++) {
650             loadViewRecursive(viewNode.children.get(i));
651         }
652     }
653 
filterNodes(String filterText)654     public void filterNodes(String filterText) {
655         this.mFilterText = filterText;
656         DrawableViewNode tree = TreeViewModel.getModel().getTree();
657         if (tree != null) {
658             tree.viewNode.filter(filterText);
659             // Force redraw
660             TreeViewModel.getModel().notifySelectionChanged();
661         }
662     }
663 
getFilterText()664     public String getFilterText() {
665         return mFilterText;
666     }
667 
668     private static class PixelPerfectAutoRefreshTask extends TimerTask {
669         @Override
run()670         public void run() {
671             HierarchyViewerDirector.getDirector().refreshPixelPerfect();
672         }
673     };
674 
setPixelPerfectAutoRefresh(boolean value)675     public void setPixelPerfectAutoRefresh(boolean value) {
676         synchronized (mPixelPerfectRefreshTimer) {
677             if (value == mAutoRefresh) {
678                 return;
679             }
680             mAutoRefresh = value;
681             if (mAutoRefresh) {
682                 mCurrentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
683                 mPixelPerfectRefreshTimer.schedule(mCurrentAutoRefreshTask,
684                         mPixelPerfectAutoRefreshInterval * 1000,
685                         mPixelPerfectAutoRefreshInterval * 1000);
686             } else {
687                 mCurrentAutoRefreshTask.cancel();
688                 mCurrentAutoRefreshTask = null;
689             }
690         }
691     }
692 
setPixelPerfectAutoRefreshInterval(int value)693     public void setPixelPerfectAutoRefreshInterval(int value) {
694         synchronized (mPixelPerfectRefreshTimer) {
695             if (mPixelPerfectAutoRefreshInterval == value) {
696                 return;
697             }
698             mPixelPerfectAutoRefreshInterval = value;
699             if (mAutoRefresh) {
700                 mCurrentAutoRefreshTask.cancel();
701                 long timeLeft =
702                         Math.max(0, mPixelPerfectAutoRefreshInterval
703                                 * 1000
704                                 - (System.currentTimeMillis() - mCurrentAutoRefreshTask
705                                         .scheduledExecutionTime()));
706                 mCurrentAutoRefreshTask = new PixelPerfectAutoRefreshTask();
707                 mPixelPerfectRefreshTimer.schedule(mCurrentAutoRefreshTask, timeLeft,
708                         mPixelPerfectAutoRefreshInterval * 1000);
709             }
710         }
711     }
712 
getPixelPerfectAutoRefreshInverval()713     public int getPixelPerfectAutoRefreshInverval() {
714         return mPixelPerfectAutoRefreshInterval;
715     }
716 }
717