| /* |
| * 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); |
| } |
| }); |
| } |
| } |