blob: 8f3e980f9726d5794c0d21f695596f884ad4ec18 [file] [log] [blame]
/*
* 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.tools.idea.ddms.screenrecord;
import com.android.ddmlib.CollectingOutputReceiver;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.ScreenRecorderOptions;
import com.intellij.CommonBundle;
import com.intellij.ide.actions.ShowFilePathAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.fileChooser.FileChooserFactory;
import com.intellij.openapi.fileChooser.FileSaverDescriptor;
import com.intellij.openapi.fileChooser.FileSaverDialog;
import com.intellij.openapi.fileTypes.NativeFileType;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileWrapper;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.Calendar;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class ScreenRecorderAction {
private static final String TITLE = "Screen Recorder";
@NonNls private static final String REMOTE_PATH = "/sdcard/ddmsrec.mp4";
private static VirtualFile ourLastSavedFolder;
private final Project myProject;
private final IDevice myDevice;
public ScreenRecorderAction(@NotNull Project p, @NotNull IDevice device) {
myProject = p;
myDevice = device;
}
public void performAction() {
final ScreenRecorderOptionsDialog dialog = new ScreenRecorderOptionsDialog(myProject);
if (!dialog.showAndGet()) {
return;
}
final ScreenRecorderOptions options = dialog.getOptions();
final CountDownLatch latch = new CountDownLatch(1);
final CollectingOutputReceiver receiver = new CollectingOutputReceiver(latch);
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
try {
myDevice.startScreenRecorder(REMOTE_PATH, options, receiver);
}
catch (Exception e) {
showError(myProject, "Unexpected error while launching screen recorder", e);
latch.countDown();
}
}
});
Task.Modal screenRecorderShellTask = new ScreenRecorderTask(myProject, myDevice, latch, receiver);
screenRecorderShellTask.setCancelText("Stop Recording");
screenRecorderShellTask.queue();
}
private static class ScreenRecorderTask extends Task.Modal {
private final IDevice myDevice;
private final CountDownLatch myCompletionLatch;
private final CollectingOutputReceiver myReceiver;
public ScreenRecorderTask(@NotNull Project project,
@NotNull IDevice device,
@NotNull CountDownLatch completionLatch,
@NotNull CollectingOutputReceiver receiver) {
super(project, TITLE, true);
myDevice = device;
myCompletionLatch = completionLatch;
myReceiver = receiver;
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
int elapsedTime = 0; // elapsed time in seconds
indicator.setIndeterminate(true);
while (true) {
try {
if (myCompletionLatch.await(1, TimeUnit.SECONDS)) {
break;
}
// update elapsed time in seconds
elapsedTime++;
indicator.setText(String.format("Recording...%1$d %2$s elapsed", elapsedTime, StringUtil.pluralize("second", elapsedTime)));
if (indicator.isCanceled()) {
// explicitly cancel the running task
myReceiver.cancel();
indicator.setText("Stopping...");
// Wait for an additional second to make sure that the command
// completed and screen recorder finishes writing the output
myCompletionLatch.await(1, TimeUnit.SECONDS);
break;
}
}
catch (InterruptedException ignored) {
}
}
}
@Override
public void onSuccess() {
pullRecording();
}
@Override
public void onCancel() {
pullRecording();
}
private void pullRecording() {
FileSaverDescriptor descriptor = new FileSaverDescriptor("Save As", "", "mp4");
FileSaverDialog saveFileDialog = FileChooserFactory.getInstance().createSaveFileDialog(descriptor, myProject);
VirtualFile baseDir = ourLastSavedFolder != null ? ourLastSavedFolder : VfsUtil.getUserHomeDir();
VirtualFileWrapper fileWrapper = saveFileDialog.save(baseDir, getDefaultFileName());
if (fileWrapper == null) {
return;
}
File f = fileWrapper.getFile();
//noinspection AssignmentToStaticFieldFromInstanceMethod
ourLastSavedFolder = VfsUtil.findFileByIoFile(f.getParentFile(), false);
new PullRecordingTask(myProject, myDevice, f.getAbsolutePath()).queue();
}
private static String getDefaultFileName() {
Calendar now = Calendar.getInstance();
return String.format("device-%tF-%tH%tM%tS", now, now, now, now);
}
}
private static class PullRecordingTask extends Task.Modal {
private final String myLocalPath;
private final IDevice myDevice;
public PullRecordingTask(@Nullable Project project, @NotNull IDevice device, @NotNull String localFilePath) {
super(project, TITLE, false);
myDevice = device;
myLocalPath = localFilePath;
}
@Override
public void run(@NotNull ProgressIndicator indicator) {
try {
myDevice.pullFile(REMOTE_PATH, myLocalPath);
}
catch (Exception e) {
showError(myProject, "Unexpected error while copying video recording from device", e);
}
}
// Tries to open the file at myLocalPath
private void openSavedFile() {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(myLocalPath);
if (file != null) {
NativeFileType.openAssociatedApplication(file);
}
}
@Override
public void onSuccess() {
assert myProject != null;
if (ShowFilePathAction.isSupported()) {
int exitCode = Messages.showYesNoCancelDialog(myProject, "Video Recording saved as " + myLocalPath, TITLE, "Open" /* Yes text */,
"Show in " + ShowFilePathAction.getFileManagerName() /* No text */,
CommonBundle.getOkButtonText() /* Cancel text */, Messages.getInformationIcon());
if (exitCode == Messages.YES) {
openSavedFile();
}
else if (exitCode == Messages.NO) {
ShowFilePathAction.openFile(new File(myLocalPath));
}
}
else if (Messages.showOkCancelDialog(myProject, "Video Recording saved as " + myLocalPath, TITLE, "Open File" /* Ok text */,
CommonBundle.getOkButtonText() /* cancel text */, Messages.getInformationIcon()) ==
Messages.OK) {
openSavedFile();
}
}
}
private static void showError(@Nullable final Project project, @NotNull final String message, @Nullable final Throwable throwable) {
ApplicationManager.getApplication().invokeLater(new Runnable() {
@Override
public void run() {
String msg = message;
if (throwable != null) {
msg += throwable.getLocalizedMessage() != null ? ": " + throwable.getLocalizedMessage() : "";
}
Messages.showErrorDialog(project, msg, TITLE);
}
});
}
}