diff --git a/maven/core-unittests/src/test/java/com/codename1/samples/AudioRecorderComponentSample.java b/maven/core-unittests/src/test/java/com/codename1/samples/AudioRecorderComponentSample.java new file mode 100644 index 0000000000..3493e8ecf0 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/samples/AudioRecorderComponentSample.java @@ -0,0 +1,177 @@ +package com.codename1.samples; + +import com.codename1.components.AudioRecorderComponent; +import com.codename1.components.ToastBar; +import com.codename1.io.FileSystemStorage; +import com.codename1.io.Log; +import com.codename1.media.MediaManager; +import com.codename1.media.MediaRecorderBuilder; +import com.codename1.ui.Button; +import com.codename1.ui.CN; +import com.codename1.ui.Dialog; +import com.codename1.ui.Form; +import com.codename1.ui.Sheet; +import com.codename1.ui.layouts.BoxLayout; +import com.codename1.ui.events.ActionEvent; +import com.codename1.ui.events.ActionListener; +import com.codename1.util.AsyncResource; +import java.io.IOException; + +/** + * Test-local copy of the AudioRecorderComponent sample used by AudioRecorderComponentSampleTest. + */ +public class AudioRecorderComponentSample { + + private Form current; + private String lastRecordingPath; + + public void start() { + if (current != null) { + current.show(); + return; + } + Form hi = new Form("Audio Recorder Sample", BoxLayout.y()); + Button record = new Button("Record Audio"); + record.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + recordAudio().onResult(new com.codename1.util.AsyncResult() { + @Override + public void onReady(String res, Throwable err) { + if (err != null) { + Log.e(err); + ToastBar.showErrorMessage(err.getMessage()); + return; + } + if (res == null) { + return; + } + try { + MediaManager.createMedia(res, false).play(); + } catch (IOException ex) { + Log.e(ex); + ToastBar.showErrorMessage(ex.getMessage()); + } + } + }); + } + }); + hi.add(record); + hi.show(); + } + + AsyncResource recordAudio() { + AsyncResource out = new AsyncResource(); + final boolean[] completed = new boolean[1]; + String mime = MediaManager.getAvailableRecordingMimeTypes()[0]; + String ext = mime.indexOf("mp3") != -1 ? "mp3" : mime.indexOf("wav") != -1 ? "wav" : mime.indexOf("aiff") != -1 ? "aiff" : "aac"; + String appHome = FileSystemStorage.getInstance().getAppHomePath(); + if (appHome == null) { + appHome = "file://app/"; + } + MediaRecorderBuilder builder = new MediaRecorderBuilder() + .path(appHome + "myaudio." + ext) + .mimeType(mime); + lastRecordingPath = builder.getPath(); + + Form host = CN.getCurrentForm(); + if (host == null) { + host = new Form("Audio Recorder Host", BoxLayout.y()); + host.show(); + } + final AudioRecorderComponent cmp = new AudioRecorderComponent(builder); + final Sheet sheet = new Sheet(Sheet.getCurrentSheet(), "Record Audio"); + sheet.getContentPane().setLayout(new com.codename1.ui.layouts.BorderLayout()); + sheet.getContentPane().add(com.codename1.ui.layouts.BorderLayout.CENTER, cmp); + + final com.codename1.ui.events.ActionListener finalizeWithPath = new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent e) { + if (!completed[0]) { + completed[0] = true; + out.complete(builder.getPath()); + } + } + }; + + final com.codename1.ui.events.ActionListener finalizeWithNull = new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent e) { + if (completed[0]) { + return; + } + completed[0] = true; + FileSystemStorage fs = FileSystemStorage.getInstance(); + if (fs.exists(builder.getPath())) { + fs.delete(builder.getPath()); + } + out.complete(null); + } + }; + + final com.codename1.ui.events.ActionListener stateHandler = new com.codename1.ui.events.ActionListener() { + @Override + public void actionPerformed(com.codename1.ui.events.ActionEvent e) { + switch (cmp.getState()) { + case Accepted: + sheet.addCloseListener(finalizeWithPath); + CN.getCurrentForm().getAnimationManager().flushAnimation(new Runnable() { + public void run() { + sheet.back(); + } + }); + break; + case Canceled: + sheet.addCloseListener(finalizeWithNull); + CN.getCurrentForm().getAnimationManager().flushAnimation(new Runnable() { + public void run() { + sheet.back(); + } + }); + break; + default: + break; + } + } + + }; + cmp.addActionListener(stateHandler); + + sheet.addCloseListener(new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent e) { + AudioRecorderComponent.RecorderState state = cmp.getState(); + if (state == AudioRecorderComponent.RecorderState.Accepted || state == AudioRecorderComponent.RecorderState.Pending) { + finalizeWithPath.actionPerformed(e); + } else { + finalizeWithNull.actionPerformed(e); + } + } + }); + sheet.addBackListener(new com.codename1.ui.events.ActionListener() { + public void actionPerformed(com.codename1.ui.events.ActionEvent e) { + AudioRecorderComponent.RecorderState state = cmp.getState(); + if (state == AudioRecorderComponent.RecorderState.Accepted || state == AudioRecorderComponent.RecorderState.Pending) { + finalizeWithPath.actionPerformed(e); + } else { + finalizeWithNull.actionPerformed(e); + } + } + }); + + sheet.show(); + return out; + } + + public String getLastRecordingPath() { + return lastRecordingPath; + } + + public void stop() { + current = com.codename1.ui.CN.getCurrentForm(); + if (current instanceof Dialog) { + ((Dialog) current).dispose(); + current = com.codename1.ui.CN.getCurrentForm(); + } + } + + public void destroy() { + } +} diff --git a/maven/core-unittests/src/test/java/com/codename1/samples/AudioRecorderComponentSampleTest.java b/maven/core-unittests/src/test/java/com/codename1/samples/AudioRecorderComponentSampleTest.java new file mode 100644 index 0000000000..1deef0fdc3 --- /dev/null +++ b/maven/core-unittests/src/test/java/com/codename1/samples/AudioRecorderComponentSampleTest.java @@ -0,0 +1,284 @@ +package com.codename1.samples; + +import com.codename1.components.AudioRecorderComponent; +import com.codename1.junit.FormTest; +import com.codename1.junit.UITestBase; +import com.codename1.media.Media; +import com.codename1.ui.Button; +import com.codename1.ui.Component; +import com.codename1.ui.Container; +import com.codename1.ui.Display; +import com.codename1.ui.Form; +import com.codename1.ui.Sheet; +import com.codename1.io.FileSystemStorage; +import com.codename1.util.AsyncResource; +import com.codename1.util.AsyncResult; +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +class AudioRecorderComponentSampleTest extends UITestBase { + + @FormTest + void acceptingRecordingCompletesResourceAndPlaysMedia() throws Exception { + implementation.setAvailableRecordingMimeTypes(new String[]{"audio/wav"}); + FakeMedia recorder = new FakeMedia(); + FakeMedia playback = new FakeMedia(); + implementation.setMediaRecorder(recorder); + implementation.setMedia(playback); + + AudioRecorderComponentSample sample = new AudioRecorderComponentSample(); + AsyncResource recording = sample.recordAudio(); + ResultHolder result = new ResultHolder(); + recording.onResult(new AsyncResult() { + public void onReady(String value, Throwable error) { + result.value.set(value); + result.error.set(error); + result.completed.set(true); + } + }); + + flushUi(); + createPlaceholderFile(sample.getLastRecordingPath()); + AudioRecorderComponent recorderComponent = findRecorderComponent(); + assertNotNull(recorderComponent); + assertEquals(AudioRecorderComponent.RecorderState.Paused, recorderComponent.getState()); + + Button recordButton = findVisibleButton(recorderComponent); + assertNotNull(recordButton); + implementation.tapComponent(recordButton); + flushUi(); + + assertTrue(recorder.playInvoked); + assertEquals(AudioRecorderComponent.RecorderState.Recording, recorderComponent.getState()); + + Button doneButton = findButtonWithText(Display.getInstance().getCurrent(), "Done"); + assertNotNull(doneButton); + implementation.tapComponent(doneButton); + flushUi(); + + Button acceptButton = findButtonWithText(Display.getInstance().getCurrent(), "Accept"); + assertNotNull(acceptButton); + implementation.tapComponent(acceptButton); + flushUi(); + waitForResult(result); + + assertTrue(result.completed.get()); + assertNull(result.error.get()); + assertNotNull(result.value.get()); + assertTrue(recorder.cleanupInvoked); + assertTrue(playback.playInvoked); + } + + @FormTest + void closingSheetWithoutAcceptingCancelsAndDeletesRecording() throws Exception { + implementation.setAvailableRecordingMimeTypes(new String[]{"audio/wav"}); + FakeMedia recorder = new FakeMedia(); + implementation.setMediaRecorder(recorder); + + AudioRecorderComponentSample sample = new AudioRecorderComponentSample(); + AsyncResource recording = sample.recordAudio(); + ResultHolder result = new ResultHolder(); + recording.onResult(new AsyncResult() { + public void onReady(String value, Throwable error) { + result.value.set(value); + result.error.set(error); + result.completed.set(true); + } + }); + + flushUi(); + String path = sample.getLastRecordingPath(); + createPlaceholderFile(path); + + Sheet sheet = Sheet.getCurrentSheet(); + assertNotNull(sheet); + sheet.back(); + flushUi(); + waitForResult(result); + + assertTrue(result.completed.get()); + assertNull(result.error.get()); + assertNull(result.value.get()); + assertFalse(FileSystemStorage.getInstance().exists(path)); + } + + private AudioRecorderComponent findRecorderComponent() { + Form current = Display.getInstance().getCurrent(); + if (current == null) { + return null; + } + return findComponent(current.getContentPane(), AudioRecorderComponent.class); + } + + private Button findVisibleButton(Container root) { + for (Component child : root) { + if (child instanceof Button) { + Button button = (Button) child; + String text = button.getText(); + if ((text == null || text.length() == 0) && button.isVisible() && button.isEnabled()) { + return button; + } + } + if (child instanceof Container) { + Button nested = findVisibleButton((Container) child); + if (nested != null) { + return nested; + } + } + } + return null; + } + + private Button findButtonWithText(Form root, String text) { + if (root == null) { + return null; + } + return findButtonWithText(root.getContentPane(), text); + } + + private Button findButtonWithText(Container container, String text) { + for (Component child : container) { + if (child instanceof Button) { + Button button = (Button) child; + if (text.equals(button.getText())) { + return button; + } + } + if (child instanceof Container) { + Button nested = findButtonWithText((Container) child, text); + if (nested != null) { + return nested; + } + } + } + return null; + } + + private T findComponent(Container root, Class type) { + for (Component child : root) { + if (type.isInstance(child)) { + return type.cast(child); + } + if (child instanceof Container) { + T nested = findComponent((Container) child, type); + if (nested != null) { + return nested; + } + } + } + return null; + } + + private void createPlaceholderFile(String path) throws IOException { + OutputStream os = FileSystemStorage.getInstance().openOutputStream(path); + try { + os.write("recording".getBytes("UTF-8")); + } finally { + os.close(); + } + } + + private void flushUi() { + flushSerialCalls(); + com.codename1.ui.DisplayTest.flushEdt(); + } + + private void waitForResult(ResultHolder result) { + for (int i = 0; i < 50; i++) { + if (result.completed.get()) { + return; + } + flushUi(); + } + } + + private static class ResultHolder { + private final AtomicReference value = new AtomicReference(); + private final AtomicReference error = new AtomicReference(); + private final AtomicBoolean completed = new AtomicBoolean(); + } + + private static class FakeMedia implements Media { + private boolean playInvoked; + private boolean pauseInvoked; + private boolean cleanupInvoked; + + public void play() { + playInvoked = true; + } + + public void pause() { + pauseInvoked = true; + } + + public void prepare() { + } + + public void cleanup() { + cleanupInvoked = true; + } + + public int getTime() { + return 0; + } + + public void setTime(int time) { + } + + public int getDuration() { + return 0; + } + + public int getVolume() { + return 0; + } + + public void setVolume(int vol) { + } + + public boolean isPlaying() { + return playInvoked; + } + + public Component getVideoComponent() { + return null; + } + + public boolean isVideo() { + return false; + } + + public boolean isFullScreen() { + return false; + } + + public void setFullScreen(boolean fullScreen) { + } + + public boolean isNativePlayerMode() { + return false; + } + + public void setNativePlayerMode(boolean nativePlayerMode) { + } + + public void setVariable(String key, Object value) { + } + + public Object getVariable(String key) { + return null; + } + + public boolean isTimeSupported() { + return true; + } + + public boolean isSeekSupported() { + return true; + } + } +}