summaryrefslogtreecommitdiff
path: root/ddms
diff options
context:
space:
mode:
authorSiva Velusamy <vsiva@google.com>2013-10-22 12:55:40 -0700
committerSiva Velusamy <vsiva@google.com>2013-11-05 14:57:24 -0800
commit76adebf9afb9df99cbdeb59975f24cf73af6d4b8 (patch)
tree5a1646ea7d424233b4ad1dddb90007d87a119f89 /ddms
parentfa5b5d680c31c6c5f19d2d4ec9d4fce1c2022528 (diff)
downloadswt-76adebf9afb9df99cbdeb59975f24cf73af6d4b8.tar.gz
ddms: Provide menu item to invoke screenrecorder
Change-Id: I74a75fcbff84b7f4e3c79cf31fa2a0abfdb03c67
Diffstat (limited to 'ddms')
-rw-r--r--ddms/app/src/main/java/com/android/ddms/UIThread.java22
-rw-r--r--ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderAction.java128
-rw-r--r--ddms/ddmuilib/src/main/java/com/android/ddmuilib/screenrecord/ScreenRecorderOptionsDialog.java232
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);
+ }
+}