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