diff options
author | Siva Velusamy <vsiva@google.com> | 2013-10-22 12:55:40 -0700 |
---|---|---|
committer | Siva Velusamy <vsiva@google.com> | 2013-11-05 14:57:24 -0800 |
commit | 76adebf9afb9df99cbdeb59975f24cf73af6d4b8 (patch) | |
tree | 5a1646ea7d424233b4ad1dddb90007d87a119f89 /ddms | |
parent | fa5b5d680c31c6c5f19d2d4ec9d4fce1c2022528 (diff) | |
download | swt-76adebf9afb9df99cbdeb59975f24cf73af6d4b8.tar.gz |
ddms: Provide menu item to invoke screenrecorder
Change-Id: I74a75fcbff84b7f4e3c79cf31fa2a0abfdb03c67
Diffstat (limited to 'ddms')
3 files changed, 379 insertions, 3 deletions
diff --git a/ddms/app/src/main/java/com/android/ddms/UIThread.java b/ddms/app/src/main/java/com/android/ddms/UIThread.java index 7680f08..627e330 100644 --- a/ddms/app/src/main/java/com/android/ddms/UIThread.java +++ b/ddms/app/src/main/java/com/android/ddms/UIThread.java @@ -54,6 +54,7 @@ import com.android.ddmuilib.logcat.LogFilter; import com.android.ddmuilib.logcat.LogPanel; import com.android.ddmuilib.logcat.LogPanel.ILogFilterStorageManager; import com.android.ddmuilib.net.NetworkPanel; +import com.android.ddmuilib.screenrecord.ScreenRecorderAction; import com.android.menubar.IMenuBarCallback; import com.android.menubar.IMenuBarEnhancer; import com.android.menubar.IMenuBarEnhancer.MenuBarMode; @@ -72,6 +73,7 @@ import org.eclipse.swt.events.MenuAdapter; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.events.ShellEvent; import org.eclipse.swt.events.ShellListener; import org.eclipse.swt.graphics.Color; @@ -856,15 +858,27 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { // so it's fine to leave it there for the other platforms. screenShotItem.setText("&Screen capture...\tCtrl-S"); screenShotItem.setAccelerator('S' | SWT.MOD1); - screenShotItem.addSelectionListener(new SelectionAdapter() { + + final MenuItem screenRecordItem = new MenuItem(deviceMenu, SWT.NONE); + screenRecordItem.setText("Screen Record"); + + SelectionListener selectionListener = new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - if (mCurrentDevice != null) { + if (mCurrentDevice == null) { + return; + } + + if (e.getSource() == screenShotItem) { ScreenShotDialog dlg = new ScreenShotDialog(shell); dlg.open(mCurrentDevice); + } else if (e.getSource() == screenRecordItem) { + new ScreenRecorderAction(shell, mCurrentDevice).performAction(); } } - }); + }; + screenShotItem.addSelectionListener(selectionListener); + screenRecordItem.addSelectionListener(selectionListener); new MenuItem(deviceMenu, SWT.SEPARATOR); @@ -953,6 +967,8 @@ public class UIThread implements IUiSelectionListener, IClientChangeListener { appStateItem.setEnabled(deviceEnabled); radioStateItem.setEnabled(deviceEnabled); logCatItem.setEnabled(deviceEnabled); + screenRecordItem.setEnabled(mCurrentDevice != null && + mCurrentDevice.supportsFeature(IDevice.Feature.SCREEN_RECORD)); } }); diff --git a/ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderAction.java b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderAction.java new file mode 100644 index 0000000..d7c57a5 --- /dev/null +++ b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderAction.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.screenrecord; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ddmlib.CollectingOutputReceiver; +import com.android.ddmlib.IDevice; +import com.android.ddmlib.ScreenRecorderOptions; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.widgets.Shell; + +import java.lang.reflect.InvocationTargetException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +public class ScreenRecorderAction { + private static final String TITLE = "Screen Recorder"; + private static final String REMOTE_PATH = "/sdcard/ddmsrec.mp4"; + + private final Shell mParentShell; + private final IDevice mDevice; + + public ScreenRecorderAction(Shell parent, IDevice device) { + mParentShell = parent; + mDevice = device; + } + + public void performAction() { + ScreenRecorderOptionsDialog optionsDialog = new ScreenRecorderOptionsDialog(mParentShell); + if (optionsDialog.open() == Window.CANCEL) { + return; + } + + final ScreenRecorderOptions options = new ScreenRecorderOptions.Builder() + .setBitRate(optionsDialog.getBitRate()) + .setSize(optionsDialog.getWidth(), optionsDialog.getHeight()) + .build(); + + final CountDownLatch latch = new CountDownLatch(1); + final CollectingOutputReceiver receiver = new CollectingOutputReceiver(latch); + + new Thread(new Runnable() { + @Override + public void run() { + try { + mDevice.startScreenRecorder(REMOTE_PATH, options, receiver); + } catch (Exception e) { + showError("Unexpected error while launching screenrecorder", e); + latch.countDown(); + } + } + }, "Screen Recorder").start(); + + try { + new ProgressMonitorDialog(mParentShell).run(true, true, new IRunnableWithProgress() { + @Override + public void run(IProgressMonitor monitor) + throws InvocationTargetException, InterruptedException { + monitor.beginTask("Recording...", IProgressMonitor.UNKNOWN); + + while (true) { + // Wait for a second to see if the command has completed + if (latch.await(1, TimeUnit.SECONDS)) { + break; + } + + // If not, check if user has cancelled + if (monitor.isCanceled()) { + receiver.cancel(); + + // wait for an additional second to make sure that the command + // completed and screenrecorder finishes writing the output + latch.await(1, TimeUnit.SECONDS); + break; + } + } + } + }); + } catch (InvocationTargetException e) { + showError("Unexpected error while recording: ", e.getTargetException()); + return; + } catch (InterruptedException ignored) { + } + + try { + mDevice.pullFile(REMOTE_PATH, optionsDialog.getDestination().getAbsolutePath()); + } catch (Exception e) { + showError("Unexpected error while copying video recording from device", e); + } + + MessageDialog.openInformation(mParentShell, TITLE, "Screen recording saved at " + + optionsDialog.getDestination().getAbsolutePath()); + } + + private void showError(@NonNull final String message, @Nullable final Throwable e) { + mParentShell.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + String msg = message; + if (e != null) { + msg += e.getLocalizedMessage() != null ? ": " + e.getLocalizedMessage() : ""; + } + MessageDialog.openError(mParentShell, TITLE, msg); + } + }); + + } +} diff --git a/ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderOptionsDialog.java b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderOptionsDialog.java new file mode 100644 index 0000000..0322e08 --- /dev/null +++ b/ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderOptionsDialog.java @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ddmuilib.screenrecord; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import java.io.File; +import java.util.Calendar; + +public class ScreenRecorderOptionsDialog extends TitleAreaDialog { + private static final int DEFAULT_BITRATE_MBPS = 4; + + private static String sLastSavedFolder = System.getProperty("user.home"); + private static String sLastFileName = suggestFileName(); + + private static int sBitRateMbps = DEFAULT_BITRATE_MBPS; + private static int sWidth = 0; + private static int sHeight = 0; + + private Text mBitRateText; + private Text mWidthText; + private Text mHeightText; + private Text mDestinationText; + + public ScreenRecorderOptionsDialog(Shell parentShell) { + super(parentShell); + setShellStyle(getShellStyle() | SWT.RESIZE); + } + + @Override + protected Control createDialogArea(Composite shell) { + setTitle("Screen Recorder Options"); + setMessage("Provide screen recorder options. Leave empty to use defaults."); + + Composite parent = (Composite) super.createDialogArea(shell); + Composite c = new Composite(parent, SWT.BORDER); + c.setLayout(new GridLayout(3, false)); + c.setLayoutData(new GridData(GridData.FILL_BOTH)); + + createLabel(c, "Bit Rate (in Mbps)"); + mBitRateText = new Text(c, SWT.BORDER); + mBitRateText.setText(Integer.toString(sBitRateMbps)); + mBitRateText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + createLabel(c, ""); // empty label for 3rd column + + createLabel(c, "Video width (in px, defaults to screen width)"); + mWidthText = new Text(c, SWT.BORDER); + mWidthText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + if (sWidth > 0) { + mWidthText.setText(Integer.toString(sWidth)); + } + createLabel(c, ""); // empty label for 3rd column + + createLabel(c, "Video height (in px, defaults to screen height)"); + mHeightText = new Text(c, SWT.BORDER); + mHeightText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + if (sHeight > 0) { + mHeightText.setText(Integer.toString(sHeight)); + } + createLabel(c, ""); // empty label for 3rd column + + ModifyListener m = new ModifyListener() { + @Override + public void modifyText(ModifyEvent modifyEvent) { + validateAndUpdateState(); + } + }; + mBitRateText.addModifyListener(m); + mWidthText.addModifyListener(m); + mHeightText.addModifyListener(m); + + createLabel(c, "Save Video as: "); + mDestinationText = new Text(c, SWT.BORDER); + mDestinationText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + mDestinationText.setText(getFilePath()); + + Button browseButton = new Button(c, SWT.PUSH); + browseButton.setText("Browse"); + browseButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent selectionEvent) { + FileDialog dlg = new FileDialog(getShell(), SWT.SAVE); + + dlg.setText("Save Video..."); + dlg.setFileName(sLastFileName != null ? sLastFileName : suggestFileName()); + if (sLastSavedFolder != null) { + dlg.setFilterPath(sLastSavedFolder); + } + dlg.setFilterNames(new String[] { "MP4 files (*.mp4)" }); + dlg.setFilterExtensions(new String[] { "*.mp4" }); + + String filePath = dlg.open(); + if (filePath != null) { + if (!filePath.endsWith(".mp4")) { + filePath += ".mp4"; + } + + mDestinationText.setText(filePath); + validateAndUpdateState(); + } + } + }); + + return c; + } + + private static String getFilePath() { + return sLastSavedFolder + File.separatorChar + sLastFileName; + } + + private static String suggestFileName() { + Calendar now = Calendar.getInstance(); + return String.format("device-%tF-%tH%tM%tS.mp4", now, now, now, now); + } + + private void createLabel(Composite c, String text) { + Label l = new Label(c, SWT.NONE); + l.setText(text); + GridData gd = new GridData(); + gd.horizontalAlignment = SWT.RIGHT; + l.setLayoutData(gd); + } + + private void validateAndUpdateState() { + int intValue; + + if ((intValue = validateInteger(mBitRateText.getText().trim(), + "Bit Rate has to be an integer")) < 0) { + return; + } + sBitRateMbps = intValue > 0 ? intValue : DEFAULT_BITRATE_MBPS; + + if ((intValue = validateInteger(mWidthText.getText().trim(), + "Recorded video resolution width has to be a valid integer.")) < 0) { + return; + } + if (intValue % 16 != 0) { + setErrorMessage("Width must be a multiple of 16"); + setOkButtonEnabled(false); + return; + } + sWidth = intValue; + + if ((intValue = validateInteger(mHeightText.getText().trim(), + "Recorded video resolution height has to be a valid integer.")) < 0) { + return; + } + if (intValue % 16 != 0) { + setErrorMessage("Height must be a multiple of 16"); + setOkButtonEnabled(false); + return; + } + sHeight = intValue; + + String filePath = mDestinationText.getText(); + File f = new File(filePath); + if (!f.getParentFile().isDirectory()) { + setErrorMessage("The path '" + f.getParentFile().getAbsolutePath() + + "' is not a valid directory."); + setOkButtonEnabled(false); + return; + } + sLastFileName = f.getName(); + sLastSavedFolder = f.getParentFile().getAbsolutePath(); + + setErrorMessage(null); + setOkButtonEnabled(true); + } + + private int validateInteger(String s, String errorMessage) { + if (!s.isEmpty()) { + try { + return Integer.parseInt(s); + } catch (NumberFormatException e) { + setErrorMessage(errorMessage); + setOkButtonEnabled(false); + return -1; + } + } + + return 0; + } + + private void setOkButtonEnabled(boolean en) { + getButton(IDialogConstants.OK_ID).setEnabled(en); + } + + public int getBitRate() { + return sBitRateMbps; + } + + public int getWidth() { + return sWidth; + } + + public int getHeight() { + return sHeight; + } + + public File getDestination() { + return new File(sLastSavedFolder, sLastFileName); + } +} |