1 /* 2 * Copyright (C) 2011 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.ide.eclipse.gltrace; 18 19 import com.android.ddmlib.AndroidDebugBridge; 20 import com.android.ddmlib.IDevice; 21 22 import org.eclipse.core.runtime.preferences.IEclipsePreferences; 23 import org.eclipse.core.runtime.preferences.InstanceScope; 24 import org.eclipse.jface.dialogs.IDialogConstants; 25 import org.eclipse.jface.dialogs.TitleAreaDialog; 26 import org.eclipse.swt.SWT; 27 import org.eclipse.swt.events.ModifyEvent; 28 import org.eclipse.swt.events.ModifyListener; 29 import org.eclipse.swt.events.SelectionAdapter; 30 import org.eclipse.swt.events.SelectionEvent; 31 import org.eclipse.swt.events.SelectionListener; 32 import org.eclipse.swt.layout.GridData; 33 import org.eclipse.swt.layout.GridLayout; 34 import org.eclipse.swt.widgets.Button; 35 import org.eclipse.swt.widgets.Combo; 36 import org.eclipse.swt.widgets.Composite; 37 import org.eclipse.swt.widgets.Control; 38 import org.eclipse.swt.widgets.Display; 39 import org.eclipse.swt.widgets.FileDialog; 40 import org.eclipse.swt.widgets.Label; 41 import org.eclipse.swt.widgets.Shell; 42 import org.eclipse.swt.widgets.Text; 43 import org.osgi.service.prefs.BackingStoreException; 44 45 import java.io.File; 46 import java.util.ArrayList; 47 import java.util.List; 48 49 /** Dialog displaying all the trace options before the user initiates tracing. */ 50 public class GLTraceOptionsDialog extends TitleAreaDialog { 51 private static final String TITLE = "OpenGL ES Trace Options"; 52 private static final String DEFAULT_MESSAGE = "Provide the application and activity to be traced."; 53 54 private static final String PREF_APP_PACKAGE = "gl.trace.apppackage"; //$NON-NLS-1$ 55 private static final String PREF_ACTIVITY = "gl.trace.activity"; //$NON-NLS-1$ 56 private static final String PREF_TRACEFILE = "gl.trace.destfile"; //$NON-NLS-1$ 57 private static final String PREF_DEVICE = "gl.trace.device"; //$NON-NLS-1$ 58 private String mLastUsedDevice; 59 60 private static String sSaveToFolder = System.getProperty("user.home"); //$NON-NLS-1$ 61 62 private Button mOkButton; 63 64 private Combo mDeviceCombo; 65 private Text mAppPackageToTraceText; 66 private Text mActivityToTraceText; 67 private Button mIsActivityFullyQualifiedButton; 68 private Text mTraceFilePathText; 69 70 private String mSelectedDevice = ""; 71 private String mAppPackageToTrace = ""; 72 private String mActivityToTrace = ""; 73 private String mTraceFilePath = ""; 74 private boolean mAllowAppSelection; 75 76 private static boolean sCollectFbOnEglSwap = true; 77 private static boolean sCollectFbOnGlDraw = false; 78 private static boolean sCollectTextureData = false; 79 private static boolean sIsActivityFullyQualified = false; 80 private IDevice[] mDevices; 81 82 public GLTraceOptionsDialog(Shell parentShell) { 83 this(parentShell, true, null); 84 } 85 86 /** 87 * Constructs a dialog displaying options for the tracer. 88 * @param allowAppSelection true if user can change the application to trace 89 * @param appToTrace default application package to trace 90 */ 91 public GLTraceOptionsDialog(Shell parentShell, boolean allowAppSelection, 92 String appToTrace) { 93 super(parentShell); 94 loadPreferences(); 95 96 mAllowAppSelection = allowAppSelection; 97 if (appToTrace != null) { 98 mAppPackageToTrace = appToTrace; 99 } 100 } 101 102 @Override 103 protected Control createDialogArea(Composite shell) { 104 setTitle(TITLE); 105 setMessage(DEFAULT_MESSAGE); 106 107 Composite parent = (Composite) super.createDialogArea(shell); 108 Composite c = new Composite(parent, SWT.BORDER); 109 c.setLayout(new GridLayout(2, false)); 110 c.setLayoutData(new GridData(GridData.FILL_BOTH)); 111 112 createLabel(c, "Device:"); 113 mDevices = AndroidDebugBridge.getBridge().getDevices(); 114 createDeviceDropdown(c, mDevices); 115 116 createSeparator(c); 117 118 createLabel(c, "Application Package:"); 119 createAppToTraceText(c, "e.g. com.example.package"); 120 121 createLabel(c, "Activity to launch:"); 122 createActivityToTraceText(c, "Leave blank to launch default activity"); 123 124 createLabel(c, ""); 125 createIsFullyQualifedActivityButton(c, 126 "Activity name is fully qualified, do not prefix with package name"); 127 128 if (!mAllowAppSelection) { 129 mAppPackageToTraceText.setEnabled(false); 130 mActivityToTraceText.setEnabled(false); 131 mIsActivityFullyQualifiedButton.setEnabled(false); 132 } 133 134 createSeparator(c); 135 136 createLabel(c, "Data Collection Options:"); 137 createCaptureImageOptions(c); 138 139 createSeparator(c); 140 141 createLabel(c, "Destination File: "); 142 createSaveToField(c); 143 144 return c; 145 } 146 147 @Override 148 protected void createButtonsForButtonBar(Composite parent) { 149 super.createButtonsForButtonBar(parent); 150 151 mOkButton = getButton(IDialogConstants.OK_ID); 152 mOkButton.setText("Trace"); 153 154 DialogStatus status = validateDialog(); 155 mOkButton.setEnabled(status.valid); 156 } 157 158 private void createSeparator(Composite c) { 159 Label l = new Label(c, SWT.SEPARATOR | SWT.HORIZONTAL); 160 GridData gd = new GridData(GridData.FILL_HORIZONTAL); 161 gd.horizontalSpan = 2; 162 l.setLayoutData(gd); 163 } 164 165 private void createSaveToField(Composite parent) { 166 Composite c = new Composite(parent, SWT.NONE); 167 c.setLayout(new GridLayout(2, false)); 168 c.setLayoutData(new GridData(GridData.FILL_BOTH)); 169 170 mTraceFilePathText = new Text(c, SWT.BORDER); 171 mTraceFilePathText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 172 mTraceFilePathText.setText(mTraceFilePath); 173 mTraceFilePathText.addModifyListener(new ModifyListener() { 174 @Override 175 public void modifyText(ModifyEvent e) { 176 validateAndSetMessage(); 177 } 178 }); 179 180 Button browse = new Button(c, SWT.PUSH); 181 browse.setText("Browse..."); 182 browse.addSelectionListener(new SelectionAdapter() { 183 @Override 184 public void widgetSelected(SelectionEvent e) { 185 String fName = openBrowseDialog(); 186 if (fName == null) { 187 return; 188 } 189 190 mTraceFilePathText.setText(fName); 191 validateAndSetMessage(); 192 } 193 }); 194 } 195 196 private String openBrowseDialog() { 197 FileDialog fd = new FileDialog(Display.getDefault().getActiveShell(), SWT.SAVE); 198 199 fd.setText("Save To"); 200 fd.setFileName("trace1.gltrace"); 201 202 fd.setFilterPath(sSaveToFolder); 203 fd.setFilterExtensions(new String[] { "*.gltrace" }); 204 205 String fname = fd.open(); 206 if (fname == null || fname.trim().length() == 0) { 207 return null; 208 } 209 210 sSaveToFolder = fd.getFilterPath(); 211 return fname; 212 } 213 214 /** Options controlling when the FB should be captured. */ 215 private void createCaptureImageOptions(Composite parent) { 216 Composite c = new Composite(parent, SWT.NONE); 217 c.setLayout(new GridLayout(1, false)); 218 c.setLayoutData(new GridData(GridData.FILL_BOTH)); 219 220 final Button readFbOnEglSwapCheckBox = new Button(c, SWT.CHECK); 221 readFbOnEglSwapCheckBox.setText("Read back framebuffer 0 on eglSwapBuffers()"); 222 readFbOnEglSwapCheckBox.setSelection(sCollectFbOnEglSwap); 223 224 final Button readFbOnGlDrawCheckBox = new Button(c, SWT.CHECK); 225 readFbOnGlDrawCheckBox.setText("Read back currently bound framebuffer On glDraw*()"); 226 readFbOnGlDrawCheckBox.setSelection(sCollectFbOnGlDraw); 227 228 final Button readTextureDataCheckBox = new Button(c, SWT.CHECK); 229 readTextureDataCheckBox.setText("Collect texture data submitted using glTexImage*()"); 230 readTextureDataCheckBox.setSelection(sCollectTextureData); 231 232 SelectionListener l = new SelectionAdapter() { 233 @Override 234 public void widgetSelected(SelectionEvent e) { 235 sCollectFbOnEglSwap = readFbOnEglSwapCheckBox.getSelection(); 236 sCollectFbOnGlDraw = readFbOnGlDrawCheckBox.getSelection(); 237 sCollectTextureData = readTextureDataCheckBox.getSelection(); 238 } 239 }; 240 241 readFbOnEglSwapCheckBox.addSelectionListener(l); 242 readFbOnGlDrawCheckBox.addSelectionListener(l); 243 readTextureDataCheckBox.addSelectionListener(l); 244 } 245 246 private Text createAppToTraceText(Composite parent, String defaultMessage) { 247 mAppPackageToTraceText = new Text(parent, SWT.BORDER); 248 mAppPackageToTraceText.setMessage(defaultMessage); 249 mAppPackageToTraceText.setText(mAppPackageToTrace); 250 251 mAppPackageToTraceText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 252 253 mAppPackageToTraceText.addModifyListener(new ModifyListener() { 254 @Override 255 public void modifyText(ModifyEvent e) { 256 validateAndSetMessage(); 257 } 258 }); 259 260 return mActivityToTraceText; 261 } 262 263 private Text createActivityToTraceText(Composite parent, String defaultMessage) { 264 mActivityToTraceText = new Text(parent, SWT.BORDER); 265 mActivityToTraceText.setMessage(defaultMessage); 266 mActivityToTraceText.setText(mActivityToTrace); 267 268 mActivityToTraceText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 269 270 mActivityToTraceText.addModifyListener(new ModifyListener() { 271 @Override 272 public void modifyText(ModifyEvent e) { 273 validateAndSetMessage(); 274 } 275 }); 276 277 return mActivityToTraceText; 278 } 279 280 private Button createIsFullyQualifedActivityButton(Composite parent, String message) { 281 mIsActivityFullyQualifiedButton = new Button(parent, SWT.CHECK); 282 mIsActivityFullyQualifiedButton.setText(message); 283 mIsActivityFullyQualifiedButton.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); 284 mIsActivityFullyQualifiedButton.setSelection(sIsActivityFullyQualified); 285 286 return mIsActivityFullyQualifiedButton; 287 } 288 289 private void validateAndSetMessage() { 290 DialogStatus status = validateDialog(); 291 mOkButton.setEnabled(status.valid); 292 setErrorMessage(status.message); 293 } 294 295 private Combo createDeviceDropdown(Composite parent, IDevice[] devices) { 296 mDeviceCombo = new Combo(parent, SWT.READ_ONLY | SWT.BORDER); 297 298 List<String> items = new ArrayList<String>(devices.length); 299 for (IDevice d : devices) { 300 items.add(d.getName()); 301 } 302 mDeviceCombo.setItems(items.toArray(new String[items.size()])); 303 304 int index = 0; 305 if (items.contains(mLastUsedDevice)) { 306 index = items.indexOf(mLastUsedDevice); 307 } 308 if (index >= 0 && index < items.size()) { 309 mDeviceCombo.select(index); 310 } 311 return mDeviceCombo; 312 } 313 314 private void createLabel(Composite parent, String text) { 315 Label l = new Label(parent, SWT.NONE); 316 l.setText(text); 317 GridData gd = new GridData(); 318 gd.horizontalAlignment = SWT.RIGHT; 319 gd.verticalAlignment = SWT.CENTER; 320 l.setLayoutData(gd); 321 } 322 323 /** 324 * A tuple that specifies whether the current state of the inputs 325 * on the dialog is valid or not. If it is not valid, the message 326 * field stores the reason why it isn't. 327 */ 328 private final class DialogStatus { 329 final boolean valid; 330 final String message; 331 332 private DialogStatus(boolean isValid, String errMessage) { 333 valid = isValid; 334 message = errMessage; 335 } 336 } 337 338 private DialogStatus validateDialog() { 339 if (mDevices.length == 0) { 340 return new DialogStatus(false, "No connected devices."); 341 } 342 343 if (mAppPackageToTraceText.getText().trim().isEmpty()) { 344 return new DialogStatus(false, "Provide an application name"); 345 } 346 347 String traceFile = mTraceFilePathText.getText().trim(); 348 if (traceFile.isEmpty()) { 349 return new DialogStatus(false, "Specify the location where the trace will be saved."); 350 } 351 352 File f = new File(traceFile).getParentFile(); 353 if (f != null && !f.exists()) { 354 return new DialogStatus(false, 355 String.format("Folder %s does not exist", f.getAbsolutePath())); 356 } 357 358 return new DialogStatus(true, null); 359 } 360 361 @Override 362 protected void okPressed() { 363 mAppPackageToTrace = mAppPackageToTraceText.getText().trim(); 364 mActivityToTrace = mActivityToTraceText.getText().trim(); 365 if (mActivityToTrace.startsWith(".")) { //$NON-NLS-1$ 366 mActivityToTrace = mActivityToTrace.substring(1); 367 } 368 sIsActivityFullyQualified = mIsActivityFullyQualifiedButton.getSelection(); 369 mTraceFilePath = mTraceFilePathText.getText().trim(); 370 mSelectedDevice = mDeviceCombo.getText(); 371 372 savePreferences(); 373 374 super.okPressed(); 375 } 376 377 private void savePreferences() { 378 IEclipsePreferences prefs = new InstanceScope().getNode(GlTracePlugin.PLUGIN_ID); 379 prefs.put(PREF_APP_PACKAGE, mAppPackageToTrace); 380 prefs.put(PREF_ACTIVITY, mActivityToTrace); 381 prefs.put(PREF_TRACEFILE, mTraceFilePath); 382 prefs.put(PREF_DEVICE, mSelectedDevice); 383 try { 384 prefs.flush(); 385 } catch (BackingStoreException e) { 386 // ignore issues while persisting preferences 387 } 388 } 389 390 private void loadPreferences() { 391 IEclipsePreferences prefs = new InstanceScope().getNode(GlTracePlugin.PLUGIN_ID); 392 mAppPackageToTrace = prefs.get(PREF_APP_PACKAGE, ""); 393 mActivityToTrace = prefs.get(PREF_ACTIVITY, ""); 394 mTraceFilePath = prefs.get(PREF_TRACEFILE, ""); 395 mLastUsedDevice = prefs.get(PREF_DEVICE, ""); 396 } 397 398 public TraceOptions getTraceOptions() { 399 return new TraceOptions(mSelectedDevice, mAppPackageToTrace, mActivityToTrace, 400 sIsActivityFullyQualified, mTraceFilePath, sCollectFbOnEglSwap, 401 sCollectFbOnGlDraw, sCollectTextureData); 402 } 403 } 404