• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 package android.media.videoeditor;
19 
20 import java.io.File;
21 import java.io.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.FileOutputStream;
24 import java.io.IOException;
25 import java.io.StringWriter;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.concurrent.Semaphore;
31 import java.util.concurrent.TimeUnit;
32 
33 import org.xmlpull.v1.XmlPullParser;
34 import org.xmlpull.v1.XmlPullParserException;
35 import org.xmlpull.v1.XmlSerializer;
36 
37 import android.graphics.Bitmap;
38 import android.graphics.Rect;
39 import android.media.videoeditor.MediaImageItem;
40 import android.media.videoeditor.MediaItem;
41 import android.media.MediaMetadataRetriever;
42 import android.util.Log;
43 import android.util.Xml;
44 import android.view.Surface;
45 import android.view.SurfaceHolder;
46 import android.os.Debug;
47 import android.os.SystemProperties;
48 import android.os.Environment;
49 
50 /**
51  * The VideoEditor implementation {@hide}
52  */
53 public class VideoEditorImpl implements VideoEditor {
54     /*
55      *  Logging
56      */
57     private static final String TAG = "VideoEditorImpl";
58 
59     /*
60      *  The project filename
61      */
62     private static final String PROJECT_FILENAME = "videoeditor.xml";
63 
64     /*
65      *  XML tags
66      */
67     private static final String TAG_PROJECT = "project";
68     private static final String TAG_MEDIA_ITEMS = "media_items";
69     private static final String TAG_MEDIA_ITEM = "media_item";
70     private static final String TAG_TRANSITIONS = "transitions";
71     private static final String TAG_TRANSITION = "transition";
72     private static final String TAG_OVERLAYS = "overlays";
73     private static final String TAG_OVERLAY = "overlay";
74     private static final String TAG_OVERLAY_USER_ATTRIBUTES = "overlay_user_attributes";
75     private static final String TAG_EFFECTS = "effects";
76     private static final String TAG_EFFECT = "effect";
77     private static final String TAG_AUDIO_TRACKS = "audio_tracks";
78     private static final String TAG_AUDIO_TRACK = "audio_track";
79 
80     private static final String ATTR_ID = "id";
81     private static final String ATTR_FILENAME = "filename";
82     private static final String ATTR_AUDIO_WAVEFORM_FILENAME = "waveform";
83     private static final String ATTR_RENDERING_MODE = "rendering_mode";
84     private static final String ATTR_ASPECT_RATIO = "aspect_ratio";
85     private static final String ATTR_REGENERATE_PCM = "regeneratePCMFlag";
86     private static final String ATTR_TYPE = "type";
87     private static final String ATTR_DURATION = "duration";
88     private static final String ATTR_START_TIME = "start_time";
89     private static final String ATTR_BEGIN_TIME = "begin_time";
90     private static final String ATTR_END_TIME = "end_time";
91     private static final String ATTR_VOLUME = "volume";
92     private static final String ATTR_BEHAVIOR = "behavior";
93     private static final String ATTR_DIRECTION = "direction";
94     private static final String ATTR_BLENDING = "blending";
95     private static final String ATTR_INVERT = "invert";
96     private static final String ATTR_MASK = "mask";
97     private static final String ATTR_BEFORE_MEDIA_ITEM_ID = "before_media_item";
98     private static final String ATTR_AFTER_MEDIA_ITEM_ID = "after_media_item";
99     private static final String ATTR_COLOR_EFFECT_TYPE = "color_type";
100     private static final String ATTR_COLOR_EFFECT_VALUE = "color_value";
101     private static final String ATTR_START_RECT_LEFT = "start_l";
102     private static final String ATTR_START_RECT_TOP = "start_t";
103     private static final String ATTR_START_RECT_RIGHT = "start_r";
104     private static final String ATTR_START_RECT_BOTTOM = "start_b";
105     private static final String ATTR_END_RECT_LEFT = "end_l";
106     private static final String ATTR_END_RECT_TOP = "end_t";
107     private static final String ATTR_END_RECT_RIGHT = "end_r";
108     private static final String ATTR_END_RECT_BOTTOM = "end_b";
109     private static final String ATTR_LOOP = "loop";
110     private static final String ATTR_MUTED = "muted";
111     private static final String ATTR_DUCK_ENABLED = "ducking_enabled";
112     private static final String ATTR_DUCK_THRESHOLD = "ducking_threshold";
113     private static final String ATTR_DUCKED_TRACK_VOLUME = "ducking_volume";
114     private static final String ATTR_GENERATED_IMAGE_CLIP = "generated_image_clip";
115     private static final String ATTR_IS_IMAGE_CLIP_GENERATED = "is_image_clip_generated";
116     private static final String ATTR_GENERATED_TRANSITION_CLIP = "generated_transition_clip";
117     private static final String ATTR_IS_TRANSITION_GENERATED = "is_transition_generated";
118     private static final String ATTR_OVERLAY_RGB_FILENAME = "overlay_rgb_filename";
119     private static final String ATTR_OVERLAY_FRAME_WIDTH = "overlay_frame_width";
120     private static final String ATTR_OVERLAY_FRAME_HEIGHT = "overlay_frame_height";
121     private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH = "resized_RGBframe_width";
122     private static final String ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT = "resized_RGBframe_height";
123     private static final int ENGINE_ACCESS_MAX_TIMEOUT_MS = 500;
124     /*
125      *  Instance variables
126      */
127     private final Semaphore mLock;
128     private final String mProjectPath;
129     private final List<MediaItem> mMediaItems = new ArrayList<MediaItem>();
130     private final List<AudioTrack> mAudioTracks = new ArrayList<AudioTrack>();
131     private final List<Transition> mTransitions = new ArrayList<Transition>();
132     private long mDurationMs;
133     private int mAspectRatio;
134 
135     /*
136      * Private Object for calling native Methods via MediaArtistNativeHelper
137      */
138     private MediaArtistNativeHelper mMANativeHelper;
139     private boolean mPreviewInProgress = false;
140     private final boolean mMallocDebug;
141 
142     /**
143      * Constructor
144      *
145      * @param projectPath - The path where the VideoEditor stores all files
146      *        related to the project
147      */
VideoEditorImpl(String projectPath)148     public VideoEditorImpl(String projectPath) throws IOException {
149         String s;
150         s = SystemProperties.get("libc.debug.malloc");
151         if (s.equals("1")) {
152             mMallocDebug = true;
153             try {
154                 dumpHeap("HeapAtStart");
155             } catch (Exception ex) {
156                 Log.e(TAG, "dumpHeap returned error in constructor");
157             }
158         } else {
159             mMallocDebug = false;
160         }
161         mLock = new Semaphore(1, true);
162         mMANativeHelper = new MediaArtistNativeHelper(projectPath, mLock, this);
163         mProjectPath = projectPath;
164         final File projectXml = new File(projectPath, PROJECT_FILENAME);
165         if (projectXml.exists()) {
166             try {
167                 load();
168             } catch (Exception ex) {
169                 ex.printStackTrace();
170                 throw new IOException(ex.toString());
171             }
172         } else {
173             mAspectRatio = MediaProperties.ASPECT_RATIO_16_9;
174             mDurationMs = 0;
175         }
176     }
177 
178     /*
179      * @return The MediaArtistNativeHelper object
180      */
getNativeContext()181     MediaArtistNativeHelper getNativeContext() {
182         return mMANativeHelper;
183     }
184 
185     /*
186      * {@inheritDoc}
187      */
addAudioTrack(AudioTrack audioTrack)188     public synchronized void addAudioTrack(AudioTrack audioTrack) {
189         if (audioTrack == null) {
190             throw new IllegalArgumentException("Audio Track is null");
191         }
192 
193         if (mAudioTracks.size() == 1) {
194             throw new IllegalArgumentException("No more tracks can be added");
195         }
196 
197         mMANativeHelper.setGeneratePreview(true);
198 
199         /*
200          * Add the audio track to AudioTrack list
201          */
202         mAudioTracks.add(audioTrack);
203 
204         /*
205          * Form the audio PCM file path
206          */
207         final String audioTrackPCMFilePath = String.format(mProjectPath + "/"
208                     + "AudioPcm" + audioTrack.getId() + ".pcm");
209 
210         /*
211          * Create PCM only if not generated in previous session
212          */
213         if (new File(audioTrackPCMFilePath).exists()) {
214             mMANativeHelper.setAudioflag(false);
215         }
216 
217     }
218 
219     /*
220      * {@inheritDoc}
221      */
addMediaItem(MediaItem mediaItem)222     public synchronized void addMediaItem(MediaItem mediaItem) {
223         /*
224          * Validate Media Item
225          */
226         if (mediaItem == null) {
227             throw new IllegalArgumentException("Media item is null");
228         }
229         /*
230          * Add the Media item to MediaItem list
231          */
232         if (mMediaItems.contains(mediaItem)) {
233             throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
234         }
235 
236         mMANativeHelper.setGeneratePreview(true);
237 
238         /*
239          *  Invalidate the end transition if necessary
240          */
241         final int mediaItemsCount = mMediaItems.size();
242         if (mediaItemsCount > 0) {
243             removeTransitionAfter(mediaItemsCount - 1);
244         }
245 
246         /*
247          *  Add the new media item
248          */
249         mMediaItems.add(mediaItem);
250 
251         computeTimelineDuration();
252 
253         /*
254          *  Generate project thumbnail only from first media Item on storyboard
255          */
256         if (mMediaItems.size() == 1) {
257             generateProjectThumbnail();
258         }
259     }
260 
261 
262     /*
263      * {@inheritDoc}
264      */
addTransition(Transition transition)265     public synchronized void addTransition(Transition transition) {
266         if (transition == null) {
267             throw new IllegalArgumentException("Null Transition");
268         }
269 
270         final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
271         final MediaItem afterMediaItem = transition.getAfterMediaItem();
272         /*
273          * Check if the MediaItems are in sequence
274          */
275         if (mMediaItems == null) {
276             throw new IllegalArgumentException("No media items are added");
277         }
278 
279         if ((afterMediaItem != null) &&  (beforeMediaItem != null)) {
280             final int afterMediaItemIndex = mMediaItems.indexOf(afterMediaItem);
281             final int beforeMediaItemIndex = mMediaItems.indexOf(beforeMediaItem);
282 
283             if ((afterMediaItemIndex == -1) || (beforeMediaItemIndex == -1)) {
284                 throw new IllegalArgumentException
285                     ("Either of the mediaItem is not found in the list");
286             }
287 
288             if (afterMediaItemIndex != (beforeMediaItemIndex - 1) ) {
289                 throw new IllegalArgumentException("MediaItems are not in sequence");
290             }
291         }
292 
293         mMANativeHelper.setGeneratePreview(true);
294 
295         mTransitions.add(transition);
296         /*
297          *  Cross reference the transitions
298          */
299         if (afterMediaItem != null) {
300             /*
301              *  If a transition already exists at the specified position then
302              *  invalidate it.
303              */
304             if (afterMediaItem.getEndTransition() != null) {
305                 afterMediaItem.getEndTransition().invalidate();
306                 mTransitions.remove(afterMediaItem.getEndTransition());
307             }
308             afterMediaItem.setEndTransition(transition);
309         }
310 
311         if (beforeMediaItem != null) {
312             /*
313              *  If a transition already exists at the specified position then
314              *  invalidate it.
315              */
316             if (beforeMediaItem.getBeginTransition() != null) {
317                 beforeMediaItem.getBeginTransition().invalidate();
318                 mTransitions.remove(beforeMediaItem.getBeginTransition());
319             }
320             beforeMediaItem.setBeginTransition(transition);
321         }
322 
323         computeTimelineDuration();
324     }
325 
326     /*
327      * {@inheritDoc}
328      */
cancelExport(String filename)329     public void cancelExport(String filename) {
330         if (mMANativeHelper != null && filename != null) {
331             mMANativeHelper.stop(filename);
332         }
333     }
334 
335     /*
336      * {@inheritDoc}
337      */
export(String filename, int height, int bitrate, int audioCodec, int videoCodec, ExportProgressListener listener)338     public void export(String filename, int height, int bitrate,
339                        int audioCodec, int videoCodec,
340                        ExportProgressListener listener)
341                        throws IOException {
342         int audcodec = 0;
343         int vidcodec = 0;
344         if (filename == null) {
345             throw new IllegalArgumentException("export: filename is null");
346         }
347 
348         final File tempPathFile = new File(filename);
349         if (tempPathFile == null) {
350             throw new IOException(filename + "can not be created");
351         }
352 
353         if (mMediaItems.size() == 0) {
354             throw new IllegalStateException("No MediaItems added");
355         }
356 
357         switch (height) {
358             case MediaProperties.HEIGHT_144:
359                 break;
360             case MediaProperties.HEIGHT_288:
361                 break;
362             case MediaProperties.HEIGHT_360:
363                 break;
364             case MediaProperties.HEIGHT_480:
365                 break;
366             case MediaProperties.HEIGHT_720:
367                 break;
368             case MediaProperties.HEIGHT_1080:
369                 break;
370 
371             default: {
372                 String message = "Unsupported height value " + height;
373                 throw new IllegalArgumentException(message);
374             }
375         }
376 
377         switch (bitrate) {
378             case MediaProperties.BITRATE_28K:
379                 break;
380             case MediaProperties.BITRATE_40K:
381                 break;
382             case MediaProperties.BITRATE_64K:
383                 break;
384             case MediaProperties.BITRATE_96K:
385                 break;
386             case MediaProperties.BITRATE_128K:
387                 break;
388             case MediaProperties.BITRATE_192K:
389                 break;
390             case MediaProperties.BITRATE_256K:
391                 break;
392             case MediaProperties.BITRATE_384K:
393                 break;
394             case MediaProperties.BITRATE_512K:
395                 break;
396             case MediaProperties.BITRATE_800K:
397                 break;
398             case MediaProperties.BITRATE_2M:
399                 break;
400             case MediaProperties.BITRATE_5M:
401                 break;
402             case MediaProperties.BITRATE_8M:
403                 break;
404 
405             default: {
406                 final String message = "Unsupported bitrate value " + bitrate;
407                 throw new IllegalArgumentException(message);
408             }
409         }
410         computeTimelineDuration();
411         final long audioBitrate = MediaArtistNativeHelper.Bitrate.BR_96_KBPS;
412         final long fileSize = (mDurationMs * (bitrate + audioBitrate)) / 8000;
413         if (MAX_SUPPORTED_FILE_SIZE <= fileSize) {
414             throw new IllegalStateException("Export Size is more than 2GB");
415         }
416         switch (audioCodec) {
417             case MediaProperties.ACODEC_AAC_LC:
418                 audcodec = MediaArtistNativeHelper.AudioFormat.AAC;
419                 break;
420             case MediaProperties.ACODEC_AMRNB:
421                 audcodec = MediaArtistNativeHelper.AudioFormat.AMR_NB;
422                 break;
423 
424             default: {
425                 String message = "Unsupported audio codec type " + audioCodec;
426                 throw new IllegalArgumentException(message);
427             }
428         }
429 
430         switch (videoCodec) {
431             case MediaProperties.VCODEC_H263:
432                 vidcodec = MediaArtistNativeHelper.VideoFormat.H263;
433                 break;
434             case MediaProperties.VCODEC_H264:
435                 vidcodec = MediaArtistNativeHelper.VideoFormat.H264;
436                 break;
437             case MediaProperties.VCODEC_MPEG4:
438                 vidcodec = MediaArtistNativeHelper.VideoFormat.MPEG4;
439                 break;
440 
441             default: {
442                 String message = "Unsupported video codec type " + videoCodec;
443                 throw new IllegalArgumentException(message);
444             }
445         }
446 
447         boolean semAcquireDone = false;
448         try {
449             lock();
450             semAcquireDone = true;
451 
452             if (mMANativeHelper == null) {
453                 throw new IllegalStateException("The video editor is not initialized");
454             }
455             mMANativeHelper.setAudioCodec(audcodec);
456             mMANativeHelper.setVideoCodec(vidcodec);
457             mMANativeHelper.export(filename, mProjectPath, height,bitrate,
458                                mMediaItems, mTransitions, mAudioTracks, listener);
459         } catch (InterruptedException  ex) {
460             Log.e(TAG, "Sem acquire NOT successful in export");
461         } finally {
462             if (semAcquireDone) {
463                 unlock();
464             }
465         }
466     }
467 
468     /*
469      * {@inheritDoc}
470      */
export(String filename, int height, int bitrate, ExportProgressListener listener)471     public void export(String filename, int height, int bitrate,
472                        ExportProgressListener listener)
473                        throws IOException {
474         int defaultAudiocodec = MediaArtistNativeHelper.AudioFormat.AAC;
475         int defaultVideocodec = MediaArtistNativeHelper.VideoFormat.H264;
476 
477         export(filename, height, bitrate, defaultAudiocodec,
478                 defaultVideocodec, listener);
479     }
480 
481     /*
482      * {@inheritDoc}
483      */
generatePreview(MediaProcessingProgressListener listener)484     public void generatePreview(MediaProcessingProgressListener listener) {
485         boolean semAcquireDone = false;
486         try {
487             lock();
488             semAcquireDone = true;
489 
490             if (mMANativeHelper == null) {
491                 throw new IllegalStateException("The video editor is not initialized");
492             }
493 
494             if ((mMediaItems.size() > 0) || (mAudioTracks.size() > 0)) {
495                 mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions, mAudioTracks,
496                         listener);
497             }
498         } catch (InterruptedException  ex) {
499             Log.e(TAG, "Sem acquire NOT successful in previewStoryBoard");
500         } finally {
501             if (semAcquireDone) {
502                 unlock();
503             }
504         }
505     }
506 
507     /*
508      * {@inheritDoc}
509      */
getAllAudioTracks()510     public List<AudioTrack> getAllAudioTracks() {
511         return mAudioTracks;
512     }
513 
514     /*
515      * {@inheritDoc}
516      */
getAllMediaItems()517     public List<MediaItem> getAllMediaItems() {
518         return mMediaItems;
519     }
520 
521     /*
522      * {@inheritDoc}
523      */
getAllTransitions()524     public List<Transition> getAllTransitions() {
525         return mTransitions;
526     }
527 
528     /*
529      * {@inheritDoc}
530      */
getAspectRatio()531     public int getAspectRatio() {
532         return mAspectRatio;
533     }
534 
535     /*
536      * {@inheritDoc}
537      */
getAudioTrack(String audioTrackId)538     public AudioTrack getAudioTrack(String audioTrackId) {
539         for (AudioTrack at : mAudioTracks) {
540             if (at.getId().equals(audioTrackId)) {
541                 return at;
542             }
543         }
544         return null;
545     }
546 
547     /*
548      * {@inheritDoc}
549      */
getDuration()550     public long getDuration() {
551         /**
552          *  Since MediaImageItem can change duration we need to compute the
553          *  duration here
554          */
555         computeTimelineDuration();
556         return mDurationMs;
557     }
558 
559     /*
560      * Force updates the timeline duration
561      */
updateTimelineDuration()562     void updateTimelineDuration() {
563         computeTimelineDuration();
564     }
565 
566     /*
567      * {@inheritDoc}
568      */
getMediaItem(String mediaItemId)569     public synchronized MediaItem getMediaItem(String mediaItemId) {
570         for (MediaItem mediaItem : mMediaItems) {
571             if (mediaItem.getId().equals(mediaItemId)) {
572                 return mediaItem;
573             }
574         }
575         return null;
576     }
577 
578     /*
579      * {@inheritDoc}
580      */
getPath()581     public String getPath() {
582         return mProjectPath;
583     }
584 
585     /*
586      * {@inheritDoc}
587      */
getTransition(String transitionId)588     public Transition getTransition(String transitionId) {
589         for (Transition transition : mTransitions) {
590             if (transition.getId().equals(transitionId)) {
591                 return transition;
592             }
593         }
594         return null;
595     }
596 
597     /*
598      * {@inheritDoc}
599      */
insertAudioTrack(AudioTrack audioTrack, String afterAudioTrackId)600     public synchronized void insertAudioTrack(AudioTrack audioTrack,
601                                               String afterAudioTrackId) {
602         if (mAudioTracks.size() == 1) {
603             throw new IllegalArgumentException("No more tracks can be added");
604         }
605 
606         if (afterAudioTrackId == null) {
607             mMANativeHelper.setGeneratePreview(true);
608             mAudioTracks.add(0, audioTrack);
609         } else {
610             final int audioTrackCount = mAudioTracks.size();
611             for (int i = 0; i < audioTrackCount; i++) {
612                 AudioTrack at = mAudioTracks.get(i);
613                 if (at.getId().equals(afterAudioTrackId)) {
614                     mMANativeHelper.setGeneratePreview(true);
615                     mAudioTracks.add(i + 1, audioTrack);
616                     return;
617                 }
618             }
619 
620             throw new IllegalArgumentException("AudioTrack not found: " + afterAudioTrackId);
621         }
622     }
623 
624     /*
625      * {@inheritDoc}
626      */
insertMediaItem(MediaItem mediaItem, String afterMediaItemId)627     public synchronized void insertMediaItem(MediaItem mediaItem, String afterMediaItemId) {
628         if (mMediaItems.contains(mediaItem)) {
629             throw new IllegalArgumentException("Media item already exists: " + mediaItem.getId());
630         }
631 
632         if (afterMediaItemId == null) {
633             mMANativeHelper.setGeneratePreview(true);
634             if (mMediaItems.size() > 0) {
635                 /**
636                  *  Invalidate the transition at the beginning of the timeline
637                  */
638                 removeTransitionBefore(0);
639             }
640 
641             mMediaItems.add(0, mediaItem);
642             computeTimelineDuration();
643             generateProjectThumbnail();
644         } else {
645             final int mediaItemCount = mMediaItems.size();
646             for (int i = 0; i < mediaItemCount; i++) {
647                 final MediaItem mi = mMediaItems.get(i);
648                 if (mi.getId().equals(afterMediaItemId)) {
649                     mMANativeHelper.setGeneratePreview(true);
650                     /**
651                      *  Invalidate the transition at this position
652                      */
653                     removeTransitionAfter(i);
654                     /**
655                      *  Insert the new media item
656                      */
657                     mMediaItems.add(i + 1, mediaItem);
658                     computeTimelineDuration();
659                     return;
660                 }
661             }
662 
663             throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
664         }
665     }
666 
667     /*
668      * {@inheritDoc}
669      */
moveAudioTrack(String audioTrackId, String afterAudioTrackId)670     public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) {
671         throw new IllegalStateException("Not supported");
672     }
673 
674     /*
675      * {@inheritDoc}
676      */
moveMediaItem(String mediaItemId, String afterMediaItemId)677     public synchronized void moveMediaItem(String mediaItemId, String afterMediaItemId) {
678         final MediaItem moveMediaItem = removeMediaItem(mediaItemId,true);
679         if (moveMediaItem == null) {
680             throw new IllegalArgumentException("Target MediaItem not found: " + mediaItemId);
681         }
682 
683         if (afterMediaItemId == null) {
684             if (mMediaItems.size() > 0) {
685                 mMANativeHelper.setGeneratePreview(true);
686 
687                 /**
688                  *  Invalidate adjacent transitions at the insertion point
689                  */
690                 removeTransitionBefore(0);
691 
692                 /**
693                  *  Insert the media item at the new position
694                  */
695                 mMediaItems.add(0, moveMediaItem);
696                 computeTimelineDuration();
697 
698                 generateProjectThumbnail();
699             } else {
700                 throw new IllegalStateException("Cannot move media item (it is the only item)");
701             }
702         } else {
703             final int mediaItemCount = mMediaItems.size();
704             for (int i = 0; i < mediaItemCount; i++) {
705                 final MediaItem mi = mMediaItems.get(i);
706                 if (mi.getId().equals(afterMediaItemId)) {
707                     mMANativeHelper.setGeneratePreview(true);
708                     /**
709                      *  Invalidate adjacent transitions at the insertion point
710                      */
711                     removeTransitionAfter(i);
712                     /**
713                      *  Insert the media item at the new position
714                      */
715                     mMediaItems.add(i + 1, moveMediaItem);
716                     computeTimelineDuration();
717                     return;
718                 }
719             }
720 
721             throw new IllegalArgumentException("MediaItem not found: " + afterMediaItemId);
722         }
723     }
724 
725     /*
726      * {@inheritDoc}
727      */
release()728     public void release() {
729         stopPreview();
730 
731         boolean semAcquireDone = false;
732         try {
733             lock();
734             semAcquireDone = true;
735 
736             if (mMANativeHelper != null) {
737                 mMediaItems.clear();
738                 mAudioTracks.clear();
739                 mTransitions.clear();
740                 mMANativeHelper.releaseNativeHelper();
741                 mMANativeHelper = null;
742             }
743         } catch (Exception  ex) {
744             Log.e(TAG, "Sem acquire NOT successful in export", ex);
745         } finally {
746             if (semAcquireDone) {
747                 unlock();
748             }
749         }
750         if (mMallocDebug) {
751             try {
752                 dumpHeap("HeapAtEnd");
753             } catch (Exception ex) {
754                 Log.e(TAG, "dumpHeap returned error in release");
755             }
756         }
757     }
758 
759     /*
760      * {@inheritDoc}
761      */
removeAllMediaItems()762     public synchronized void removeAllMediaItems() {
763         mMANativeHelper.setGeneratePreview(true);
764 
765         mMediaItems.clear();
766 
767         /**
768          *  Invalidate all transitions
769          */
770         for (Transition transition : mTransitions) {
771             transition.invalidate();
772         }
773         mTransitions.clear();
774 
775         mDurationMs = 0;
776         /**
777          * If a thumbnail already exists, then delete it
778          */
779         if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
780             (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
781         }
782 
783     }
784 
785     /*
786      * {@inheritDoc}
787      */
removeAudioTrack(String audioTrackId)788     public synchronized AudioTrack removeAudioTrack(String audioTrackId) {
789         final AudioTrack audioTrack = getAudioTrack(audioTrackId);
790         if (audioTrack != null) {
791             mMANativeHelper.setGeneratePreview(true);
792             mAudioTracks.remove(audioTrack);
793             audioTrack.invalidate();
794             mMANativeHelper.invalidatePcmFile();
795             mMANativeHelper.setAudioflag(true);
796         } else {
797             throw new IllegalArgumentException(" No more audio tracks");
798         }
799         return audioTrack;
800     }
801 
802     /*
803      * {@inheritDoc}
804      */
removeMediaItem(String mediaItemId)805     public synchronized MediaItem removeMediaItem(String mediaItemId) {
806         final String firstItemString = mMediaItems.get(0).getId();
807         final MediaItem mediaItem = getMediaItem(mediaItemId);
808         if (mediaItem != null) {
809             mMANativeHelper.setGeneratePreview(true);
810             /**
811              *  Remove the media item
812              */
813             mMediaItems.remove(mediaItem);
814             if (mediaItem instanceof MediaImageItem) {
815                 ((MediaImageItem)mediaItem).invalidate();
816             }
817             final List<Overlay> overlays = mediaItem.getAllOverlays();
818             if (overlays.size() > 0) {
819                 for (Overlay overlay : overlays) {
820                     if (overlay instanceof OverlayFrame) {
821                         final OverlayFrame overlayFrame = (OverlayFrame)overlay;
822                         overlayFrame.invalidate();
823                     }
824                 }
825             }
826 
827             /**
828              *  Remove the adjacent transitions
829              */
830             removeAdjacentTransitions(mediaItem);
831             computeTimelineDuration();
832         }
833 
834         /**
835          * If string equals first mediaItem, then
836          * generate Project thumbnail
837          */
838         if (firstItemString.equals(mediaItemId)) {
839             generateProjectThumbnail();
840         }
841 
842         if (mediaItem instanceof MediaVideoItem) {
843             /**
844              * Delete the graph file
845              */
846             ((MediaVideoItem)mediaItem).invalidate();
847         }
848         return mediaItem;
849     }
850 
removeMediaItem(String mediaItemId, boolean flag)851     private synchronized MediaItem removeMediaItem(String mediaItemId, boolean flag) {
852         final String firstItemString = mMediaItems.get(0).getId();
853 
854         final MediaItem mediaItem = getMediaItem(mediaItemId);
855         if (mediaItem != null) {
856             mMANativeHelper.setGeneratePreview(true);
857             /**
858              *  Remove the media item
859              */
860             mMediaItems.remove(mediaItem);
861             /**
862              *  Remove the adjacent transitions
863              */
864             removeAdjacentTransitions(mediaItem);
865             computeTimelineDuration();
866         }
867 
868         /**
869          * If string equals first mediaItem, then
870          * generate Project thumbail
871          */
872         if (firstItemString.equals(mediaItemId)) {
873             generateProjectThumbnail();
874         }
875         return mediaItem;
876     }
877 
878     /*
879      * {@inheritDoc}
880      */
removeTransition(String transitionId)881     public synchronized Transition removeTransition(String transitionId) {
882         final Transition transition = getTransition(transitionId);
883         if (transition == null) {
884             throw new IllegalStateException("Transition not found: " + transitionId);
885         }
886 
887         mMANativeHelper.setGeneratePreview(true);
888 
889         /**
890          *  Remove the transition references
891          */
892         final MediaItem afterMediaItem = transition.getAfterMediaItem();
893         if (afterMediaItem != null) {
894             afterMediaItem.setEndTransition(null);
895         }
896 
897         final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
898         if (beforeMediaItem != null) {
899             beforeMediaItem.setBeginTransition(null);
900         }
901 
902         mTransitions.remove(transition);
903         transition.invalidate();
904         computeTimelineDuration();
905         return transition;
906     }
907 
908     /*
909      * {@inheritDoc}
910      */
renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs, OverlayData overlayData)911     public long renderPreviewFrame(SurfaceHolder surfaceHolder, long timeMs,
912                                     OverlayData overlayData) {
913         if (surfaceHolder == null) {
914             throw new IllegalArgumentException("Surface Holder is null");
915         }
916 
917         final Surface surface = surfaceHolder.getSurface();
918         if (surface == null) {
919             throw new IllegalArgumentException("Surface could not be retrieved from Surface holder");
920         }
921 
922         if (surface.isValid() == false) {
923             throw new IllegalStateException("Surface is not valid");
924         }
925 
926         if (timeMs < 0) {
927             throw new IllegalArgumentException("requested time not correct");
928         } else if (timeMs > mDurationMs) {
929             throw new IllegalArgumentException("requested time more than duration");
930         }
931         long result = 0;
932 
933         boolean semAcquireDone = false;
934         try {
935             semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS);
936             if (semAcquireDone == false) {
937                 throw new IllegalStateException("Timeout waiting for semaphore");
938             }
939 
940             if (mMANativeHelper == null) {
941                 throw new IllegalStateException("The video editor is not initialized");
942             }
943 
944             if (mMediaItems.size() > 0) {
945                 final Rect frame = surfaceHolder.getSurfaceFrame();
946                 result = mMANativeHelper.renderPreviewFrame(surface,
947                         timeMs, frame.width(), frame.height(), overlayData);
948             } else {
949                 result = 0;
950             }
951         } catch (InterruptedException ex) {
952             Log.w(TAG, "The thread was interrupted", new Throwable());
953             throw new IllegalStateException("The thread was interrupted");
954         } finally {
955             if (semAcquireDone) {
956                 unlock();
957             }
958         }
959         return result;
960     }
961 
962     /**
963      *  the project form XML
964      */
load()965     private void load() throws FileNotFoundException, XmlPullParserException, IOException {
966         final File file = new File(mProjectPath, PROJECT_FILENAME);
967         /**
968          *  Load the metadata
969          */
970         final FileInputStream fis = new FileInputStream(file);
971         try {
972             final List<String> ignoredMediaItems = new ArrayList<String>();
973 
974             final XmlPullParser parser = Xml.newPullParser();
975             parser.setInput(fis, "UTF-8");
976             int eventType = parser.getEventType();
977             String name;
978             MediaItem currentMediaItem = null;
979             Overlay currentOverlay = null;
980             boolean regenerateProjectThumbnail = false;
981             while (eventType != XmlPullParser.END_DOCUMENT) {
982                 switch (eventType) {
983                     case XmlPullParser.START_TAG: {
984                         name = parser.getName();
985                         if (TAG_PROJECT.equals(name)) {
986                             mAspectRatio = Integer.parseInt(parser.getAttributeValue("",
987                                    ATTR_ASPECT_RATIO));
988 
989                             final boolean mRegenPCM =
990                                 Boolean.parseBoolean(parser.getAttributeValue("",
991                                     ATTR_REGENERATE_PCM));
992                             mMANativeHelper.setAudioflag(mRegenPCM);
993                         } else if (TAG_MEDIA_ITEM.equals(name)) {
994                             final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
995                             try {
996                                 currentMediaItem = parseMediaItem(parser);
997                                 mMediaItems.add(currentMediaItem);
998                             } catch (Exception ex) {
999                                 Log.w(TAG, "Cannot load media item: " + mediaItemId, ex);
1000                                 currentMediaItem = null;
1001 
1002                                 // First media item is invalid, mark for project thumbnail removal
1003                                 if (mMediaItems.size() == 0) {
1004                                     regenerateProjectThumbnail = true;
1005                                 }
1006                                 // Ignore the media item
1007                                 ignoredMediaItems.add(mediaItemId);
1008                             }
1009                         } else if (TAG_TRANSITION.equals(name)) {
1010                             try {
1011                                 final Transition transition = parseTransition(parser,
1012                                         ignoredMediaItems);
1013                                 // The transition will be null if the bounding
1014                                 // media items are ignored
1015                                 if (transition != null) {
1016                                     mTransitions.add(transition);
1017                                 }
1018                             } catch (Exception ex) {
1019                                 Log.w(TAG, "Cannot load transition", ex);
1020                             }
1021                         } else if (TAG_OVERLAY.equals(name)) {
1022                             if (currentMediaItem != null) {
1023                                 try {
1024                                     currentOverlay = parseOverlay(parser, currentMediaItem);
1025                                     currentMediaItem.addOverlay(currentOverlay);
1026                                 } catch (Exception ex) {
1027                                     Log.w(TAG, "Cannot load overlay", ex);
1028                                 }
1029                             }
1030                         } else if (TAG_OVERLAY_USER_ATTRIBUTES.equals(name)) {
1031                             if (currentOverlay != null) {
1032                                 final int attributesCount = parser.getAttributeCount();
1033                                 for (int i = 0; i < attributesCount; i++) {
1034                                     currentOverlay.setUserAttribute(parser.getAttributeName(i),
1035                                             parser.getAttributeValue(i));
1036                                 }
1037                             }
1038                         } else if (TAG_EFFECT.equals(name)) {
1039                             if (currentMediaItem != null) {
1040                                 try {
1041                                     final Effect effect = parseEffect(parser, currentMediaItem);
1042                                     currentMediaItem.addEffect(effect);
1043 
1044                                     if (effect instanceof EffectKenBurns) {
1045                                         final boolean isImageClipGenerated =
1046                                                Boolean.parseBoolean(parser.getAttributeValue("",
1047                                                                   ATTR_IS_IMAGE_CLIP_GENERATED));
1048                                         if(isImageClipGenerated) {
1049                                             final String filename = parser.getAttributeValue("",
1050                                                                   ATTR_GENERATED_IMAGE_CLIP);
1051                                             if (new File(filename).exists() == true) {
1052                                                 ((MediaImageItem)currentMediaItem).
1053                                                             setGeneratedImageClip(filename);
1054                                                 ((MediaImageItem)currentMediaItem).
1055                                                              setRegenerateClip(false);
1056                                              } else {
1057                                                ((MediaImageItem)currentMediaItem).
1058                                                              setGeneratedImageClip(null);
1059                                                ((MediaImageItem)currentMediaItem).
1060                                                              setRegenerateClip(true);
1061                                              }
1062                                         } else {
1063                                             ((MediaImageItem)currentMediaItem).
1064                                                              setGeneratedImageClip(null);
1065                                             ((MediaImageItem)currentMediaItem).
1066                                                             setRegenerateClip(true);
1067                                         }
1068                                     }
1069                                 } catch (Exception ex) {
1070                                     Log.w(TAG, "Cannot load effect", ex);
1071                                 }
1072                             }
1073                         } else if (TAG_AUDIO_TRACK.equals(name)) {
1074                             try {
1075                                 final AudioTrack audioTrack = parseAudioTrack(parser);
1076                                 addAudioTrack(audioTrack);
1077                             } catch (Exception ex) {
1078                                 Log.w(TAG, "Cannot load audio track", ex);
1079                             }
1080                         }
1081                         break;
1082                     }
1083 
1084                     case XmlPullParser.END_TAG: {
1085                         name = parser.getName();
1086                         if (TAG_MEDIA_ITEM.equals(name)) {
1087                             currentMediaItem = null;
1088                         } else if (TAG_OVERLAY.equals(name)) {
1089                             currentOverlay = null;
1090                         }
1091                         break;
1092                     }
1093 
1094                     default: {
1095                         break;
1096                     }
1097                 }
1098                 eventType = parser.next();
1099             }
1100             computeTimelineDuration();
1101             // Regenerate project thumbnail
1102             if (regenerateProjectThumbnail) {
1103                 generateProjectThumbnail();
1104                 regenerateProjectThumbnail = false;
1105             }
1106         } finally {
1107             if (fis != null) {
1108                 fis.close();
1109             }
1110         }
1111     }
1112 
1113     /**
1114      * Parse the media item
1115      *
1116      * @param parser The parser
1117      * @return The media item
1118      */
parseMediaItem(XmlPullParser parser)1119     private MediaItem parseMediaItem(XmlPullParser parser) throws IOException {
1120         final String mediaItemId = parser.getAttributeValue("", ATTR_ID);
1121         final String type = parser.getAttributeValue("", ATTR_TYPE);
1122         final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1123         final int renderingMode = Integer.parseInt(parser.getAttributeValue("",
1124                 ATTR_RENDERING_MODE));
1125 
1126         final MediaItem currentMediaItem;
1127         if (MediaImageItem.class.getSimpleName().equals(type)) {
1128             final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1129             currentMediaItem = new MediaImageItem(this, mediaItemId, filename,
1130                     durationMs, renderingMode);
1131         } else if (MediaVideoItem.class.getSimpleName().equals(type)) {
1132             final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1133             final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1134             final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1135             final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
1136             final String audioWaveformFilename = parser.getAttributeValue("",
1137                     ATTR_AUDIO_WAVEFORM_FILENAME);
1138             currentMediaItem = new MediaVideoItem(this, mediaItemId, filename,
1139                     renderingMode, beginMs, endMs, volume, muted, audioWaveformFilename);
1140 
1141             final long beginTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1142             final long endTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1143             ((MediaVideoItem)currentMediaItem).setExtractBoundaries(beginTimeMs, endTimeMs);
1144 
1145             final int volumePercent = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1146             ((MediaVideoItem)currentMediaItem).setVolume(volumePercent);
1147         } else {
1148             throw new IllegalArgumentException("Unknown media item type: " + type);
1149         }
1150 
1151         return currentMediaItem;
1152     }
1153 
1154     /**
1155      * Parse the transition
1156      *
1157      * @param parser The parser
1158      * @param ignoredMediaItems The list of ignored media items
1159      *
1160      * @return The transition
1161      */
parseTransition(XmlPullParser parser, List<String> ignoredMediaItems)1162     private Transition parseTransition(XmlPullParser parser, List<String> ignoredMediaItems) {
1163         final String transitionId = parser.getAttributeValue("", ATTR_ID);
1164         final String type = parser.getAttributeValue("", ATTR_TYPE);
1165         final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1166         final int behavior = Integer.parseInt(parser.getAttributeValue("", ATTR_BEHAVIOR));
1167 
1168         final String beforeMediaItemId = parser.getAttributeValue("", ATTR_BEFORE_MEDIA_ITEM_ID);
1169         final MediaItem beforeMediaItem;
1170         if (beforeMediaItemId != null) {
1171             if (ignoredMediaItems.contains(beforeMediaItemId)) {
1172                 // This transition is ignored
1173                 return null;
1174             }
1175 
1176             beforeMediaItem = getMediaItem(beforeMediaItemId);
1177         } else {
1178             beforeMediaItem = null;
1179         }
1180 
1181         final String afterMediaItemId = parser.getAttributeValue("", ATTR_AFTER_MEDIA_ITEM_ID);
1182         final MediaItem afterMediaItem;
1183         if (afterMediaItemId != null) {
1184             if (ignoredMediaItems.contains(afterMediaItemId)) {
1185                 // This transition is ignored
1186                 return null;
1187             }
1188 
1189             afterMediaItem = getMediaItem(afterMediaItemId);
1190         } else {
1191             afterMediaItem = null;
1192         }
1193 
1194         final Transition transition;
1195         if (TransitionAlpha.class.getSimpleName().equals(type)) {
1196             final int blending = Integer.parseInt(parser.getAttributeValue("", ATTR_BLENDING));
1197             final String maskFilename = parser.getAttributeValue("", ATTR_MASK);
1198             final boolean invert = Boolean.getBoolean(parser.getAttributeValue("", ATTR_INVERT));
1199             transition = new TransitionAlpha(transitionId, afterMediaItem, beforeMediaItem,
1200                     durationMs, behavior, maskFilename, blending, invert);
1201         } else if (TransitionCrossfade.class.getSimpleName().equals(type)) {
1202             transition = new TransitionCrossfade(transitionId, afterMediaItem, beforeMediaItem,
1203                     durationMs, behavior);
1204         } else if (TransitionSliding.class.getSimpleName().equals(type)) {
1205             final int direction = Integer.parseInt(parser.getAttributeValue("", ATTR_DIRECTION));
1206             transition = new TransitionSliding(transitionId, afterMediaItem, beforeMediaItem,
1207                     durationMs, behavior, direction);
1208         } else if (TransitionFadeBlack.class.getSimpleName().equals(type)) {
1209             transition = new TransitionFadeBlack(transitionId, afterMediaItem, beforeMediaItem,
1210                     durationMs, behavior);
1211         } else {
1212             throw new IllegalArgumentException("Invalid transition type: " + type);
1213         }
1214 
1215         final boolean isTransitionGenerated = Boolean.parseBoolean(parser.getAttributeValue("",
1216                                                  ATTR_IS_TRANSITION_GENERATED));
1217         if (isTransitionGenerated == true) {
1218             final String transitionFile = parser.getAttributeValue("",
1219                                                 ATTR_GENERATED_TRANSITION_CLIP);
1220 
1221             if (new File(transitionFile).exists()) {
1222                 transition.setFilename(transitionFile);
1223             } else {
1224                 transition.setFilename(null);
1225             }
1226         }
1227 
1228         // Use the transition
1229         if (beforeMediaItem != null) {
1230             beforeMediaItem.setBeginTransition(transition);
1231         }
1232 
1233         if (afterMediaItem != null) {
1234             afterMediaItem.setEndTransition(transition);
1235         }
1236 
1237         return transition;
1238     }
1239 
1240     /**
1241      * Parse the overlay
1242      *
1243      * @param parser The parser
1244      * @param mediaItem The media item owner
1245      *
1246      * @return The overlay
1247      */
parseOverlay(XmlPullParser parser, MediaItem mediaItem)1248     private Overlay parseOverlay(XmlPullParser parser, MediaItem mediaItem) {
1249         final String overlayId = parser.getAttributeValue("", ATTR_ID);
1250         final String type = parser.getAttributeValue("", ATTR_TYPE);
1251         final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1252         final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1253 
1254         final Overlay overlay;
1255         if (OverlayFrame.class.getSimpleName().equals(type)) {
1256             final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1257             overlay = new OverlayFrame(mediaItem, overlayId, filename, startTimeMs, durationMs);
1258         } else {
1259             throw new IllegalArgumentException("Invalid overlay type: " + type);
1260         }
1261 
1262         final String overlayRgbFileName = parser.getAttributeValue("", ATTR_OVERLAY_RGB_FILENAME);
1263         if (overlayRgbFileName != null) {
1264             ((OverlayFrame)overlay).setFilename(overlayRgbFileName);
1265 
1266             final int overlayFrameWidth = Integer.parseInt(parser.getAttributeValue("",
1267                                    ATTR_OVERLAY_FRAME_WIDTH));
1268             final int overlayFrameHeight = Integer.parseInt(parser.getAttributeValue("",
1269                                    ATTR_OVERLAY_FRAME_HEIGHT));
1270 
1271             ((OverlayFrame)overlay).setOverlayFrameWidth(overlayFrameWidth);
1272             ((OverlayFrame)overlay).setOverlayFrameHeight(overlayFrameHeight);
1273 
1274             final int resizedRGBFrameWidth = Integer.parseInt(parser.getAttributeValue("",
1275                                    ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH));
1276             final int resizedRGBFrameHeight = Integer.parseInt(parser.getAttributeValue("",
1277                                    ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT));
1278 
1279             ((OverlayFrame)overlay).setResizedRGBSize(resizedRGBFrameWidth, resizedRGBFrameHeight);
1280         }
1281 
1282         return overlay;
1283     }
1284 
1285     /**
1286      * Parse the effect
1287      *
1288      * @param parser The parser
1289      * @param mediaItem The media item owner
1290      *
1291      * @return The effect
1292      */
parseEffect(XmlPullParser parser, MediaItem mediaItem)1293     private Effect parseEffect(XmlPullParser parser, MediaItem mediaItem) {
1294         final String effectId = parser.getAttributeValue("", ATTR_ID);
1295         final String type = parser.getAttributeValue("", ATTR_TYPE);
1296         final long durationMs = Long.parseLong(parser.getAttributeValue("", ATTR_DURATION));
1297         final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1298 
1299         final Effect effect;
1300         if (EffectColor.class.getSimpleName().equals(type)) {
1301             final int colorEffectType = Integer.parseInt(parser.getAttributeValue("",
1302                                                        ATTR_COLOR_EFFECT_TYPE));
1303             final int color;
1304             if (colorEffectType == EffectColor.TYPE_COLOR
1305                     || colorEffectType == EffectColor.TYPE_GRADIENT) {
1306                 color = Integer.parseInt(parser.getAttributeValue("", ATTR_COLOR_EFFECT_VALUE));
1307             } else {
1308                 color = 0;
1309             }
1310             effect = new EffectColor(mediaItem, effectId, startTimeMs,
1311                     durationMs, colorEffectType, color);
1312         } else if (EffectKenBurns.class.getSimpleName().equals(type)) {
1313             final Rect startRect = new Rect(
1314                     Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_LEFT)),
1315                     Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_TOP)),
1316                     Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_RIGHT)),
1317                     Integer.parseInt(parser.getAttributeValue("", ATTR_START_RECT_BOTTOM)));
1318             final Rect endRect = new Rect(
1319                     Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_LEFT)),
1320                     Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_TOP)),
1321                     Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_RIGHT)),
1322                     Integer.parseInt(parser.getAttributeValue("", ATTR_END_RECT_BOTTOM)));
1323             effect = new EffectKenBurns(mediaItem, effectId, startRect, endRect,
1324                                         startTimeMs, durationMs);
1325         } else {
1326             throw new IllegalArgumentException("Invalid effect type: " + type);
1327         }
1328 
1329         return effect;
1330     }
1331 
1332     /**
1333      * Parse the audio track
1334      *
1335      * @param parser The parser
1336      *
1337      * @return The audio track
1338      */
parseAudioTrack(XmlPullParser parser)1339     private AudioTrack parseAudioTrack(XmlPullParser parser) throws IOException {
1340         final String audioTrackId = parser.getAttributeValue("", ATTR_ID);
1341         final String filename = parser.getAttributeValue("", ATTR_FILENAME);
1342         final long startTimeMs = Long.parseLong(parser.getAttributeValue("", ATTR_START_TIME));
1343         final long beginMs = Long.parseLong(parser.getAttributeValue("", ATTR_BEGIN_TIME));
1344         final long endMs = Long.parseLong(parser.getAttributeValue("", ATTR_END_TIME));
1345         final int volume = Integer.parseInt(parser.getAttributeValue("", ATTR_VOLUME));
1346         final boolean muted = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_MUTED));
1347         final boolean loop = Boolean.parseBoolean(parser.getAttributeValue("", ATTR_LOOP));
1348         final boolean duckingEnabled = Boolean.parseBoolean(
1349                 parser.getAttributeValue("", ATTR_DUCK_ENABLED));
1350         final int duckThreshold = Integer.parseInt(
1351                 parser.getAttributeValue("", ATTR_DUCK_THRESHOLD));
1352         final int duckedTrackVolume = Integer.parseInt(parser.getAttributeValue("",
1353                                                      ATTR_DUCKED_TRACK_VOLUME));
1354 
1355         final String waveformFilename = parser.getAttributeValue("", ATTR_AUDIO_WAVEFORM_FILENAME);
1356         final AudioTrack audioTrack = new AudioTrack(this, audioTrackId,
1357                                                      filename, startTimeMs,
1358                                                      beginMs, endMs, loop,
1359                                                      volume, muted,
1360                                                      duckingEnabled,
1361                                                      duckThreshold,
1362                                                      duckedTrackVolume,
1363                                                      waveformFilename);
1364 
1365         return audioTrack;
1366     }
1367 
1368     /*
1369      * {@inheritDoc}
1370      */
save()1371     public void save() throws IOException {
1372         final XmlSerializer serializer = Xml.newSerializer();
1373         final StringWriter writer = new StringWriter();
1374         serializer.setOutput(writer);
1375         serializer.startDocument("UTF-8", true);
1376         serializer.startTag("", TAG_PROJECT);
1377         serializer.attribute("",
1378                              ATTR_ASPECT_RATIO, Integer.toString(mAspectRatio));
1379 
1380         serializer.attribute("", ATTR_REGENERATE_PCM,
1381                         Boolean.toString(mMANativeHelper.getAudioflag()));
1382 
1383         serializer.startTag("", TAG_MEDIA_ITEMS);
1384         for (MediaItem mediaItem : mMediaItems) {
1385             serializer.startTag("", TAG_MEDIA_ITEM);
1386             serializer.attribute("", ATTR_ID, mediaItem.getId());
1387             serializer.attribute("", ATTR_TYPE,
1388                                           mediaItem.getClass().getSimpleName());
1389             serializer.attribute("", ATTR_FILENAME, mediaItem.getFilename());
1390             serializer.attribute("", ATTR_RENDERING_MODE, Integer.toString(
1391                     mediaItem.getRenderingMode()));
1392             if (mediaItem instanceof MediaVideoItem) {
1393                 final MediaVideoItem mvi = (MediaVideoItem)mediaItem;
1394                 serializer
1395                 .attribute("", ATTR_BEGIN_TIME,
1396                                      Long.toString(mvi.getBoundaryBeginTime()));
1397                 serializer.attribute("", ATTR_END_TIME,
1398                                        Long.toString(mvi.getBoundaryEndTime()));
1399                 serializer.attribute("", ATTR_VOLUME,
1400                                              Integer.toString(mvi.getVolume()));
1401                 serializer.attribute("", ATTR_MUTED,
1402                                                Boolean.toString(mvi.isMuted()));
1403                 if (mvi.getAudioWaveformFilename() != null) {
1404                     serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1405                             mvi.getAudioWaveformFilename());
1406                 }
1407             } else if (mediaItem instanceof MediaImageItem) {
1408                 serializer.attribute("", ATTR_DURATION,
1409                         Long.toString(mediaItem.getTimelineDuration()));
1410             }
1411 
1412             final List<Overlay> overlays = mediaItem.getAllOverlays();
1413             if (overlays.size() > 0) {
1414                 serializer.startTag("", TAG_OVERLAYS);
1415                 for (Overlay overlay : overlays) {
1416                     serializer.startTag("", TAG_OVERLAY);
1417                     serializer.attribute("", ATTR_ID, overlay.getId());
1418                     serializer.attribute("",
1419                                  ATTR_TYPE, overlay.getClass().getSimpleName());
1420                     serializer.attribute("", ATTR_BEGIN_TIME,
1421                                          Long.toString(overlay.getStartTime()));
1422                     serializer.attribute("", ATTR_DURATION,
1423                                           Long.toString(overlay.getDuration()));
1424                     if (overlay instanceof OverlayFrame) {
1425                         final OverlayFrame overlayFrame = (OverlayFrame)overlay;
1426                         overlayFrame.save(getPath());
1427                         if (overlayFrame.getBitmapImageFileName() != null) {
1428                             serializer.attribute("", ATTR_FILENAME,
1429                                          overlayFrame.getBitmapImageFileName());
1430                         }
1431 
1432                         if (overlayFrame.getFilename() != null) {
1433                             serializer.attribute("",
1434                                                  ATTR_OVERLAY_RGB_FILENAME,
1435                                                  overlayFrame.getFilename());
1436                             serializer.attribute("", ATTR_OVERLAY_FRAME_WIDTH,
1437                                                  Integer.toString(overlayFrame.getOverlayFrameWidth()));
1438                             serializer.attribute("", ATTR_OVERLAY_FRAME_HEIGHT,
1439                                                  Integer.toString(overlayFrame.getOverlayFrameHeight()));
1440                             serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_WIDTH,
1441                                                  Integer.toString(overlayFrame.getResizedRGBSizeWidth()));
1442                             serializer.attribute("", ATTR_OVERLAY_RESIZED_RGB_FRAME_HEIGHT,
1443                                                  Integer.toString(overlayFrame.getResizedRGBSizeHeight()));
1444 
1445                         }
1446 
1447                     }
1448 
1449                     /**
1450                      *  Save the user attributes
1451                      */
1452                     serializer.startTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1453                     final Map<String, String> userAttributes = overlay.getUserAttributes();
1454                     for (String name : userAttributes.keySet()) {
1455                         final String value = userAttributes.get(name);
1456                         if (value != null) {
1457                             serializer.attribute("", name, value);
1458                         }
1459                     }
1460                     serializer.endTag("", TAG_OVERLAY_USER_ATTRIBUTES);
1461 
1462                     serializer.endTag("", TAG_OVERLAY);
1463                 }
1464                 serializer.endTag("", TAG_OVERLAYS);
1465             }
1466 
1467             final List<Effect> effects = mediaItem.getAllEffects();
1468             if (effects.size() > 0) {
1469                 serializer.startTag("", TAG_EFFECTS);
1470                 for (Effect effect : effects) {
1471                     serializer.startTag("", TAG_EFFECT);
1472                     serializer.attribute("", ATTR_ID, effect.getId());
1473                     serializer.attribute("",
1474                                   ATTR_TYPE, effect.getClass().getSimpleName());
1475                     serializer.attribute("", ATTR_BEGIN_TIME,
1476                             Long.toString(effect.getStartTime()));
1477                     serializer.attribute("", ATTR_DURATION,
1478                                            Long.toString(effect.getDuration()));
1479                     if (effect instanceof EffectColor) {
1480                         final EffectColor colorEffect = (EffectColor)effect;
1481                         serializer.attribute("", ATTR_COLOR_EFFECT_TYPE,
1482                                 Integer.toString(colorEffect.getType()));
1483                         if (colorEffect.getType() == EffectColor.TYPE_COLOR ||
1484                                 colorEffect.getType() == EffectColor.TYPE_GRADIENT) {
1485                             serializer.attribute("", ATTR_COLOR_EFFECT_VALUE,
1486                                     Integer.toString(colorEffect.getColor()));
1487                         }
1488                     } else if (effect instanceof EffectKenBurns) {
1489                         final Rect startRect = ((EffectKenBurns)effect).getStartRect();
1490                         serializer.attribute("", ATTR_START_RECT_LEFT,
1491                                 Integer.toString(startRect.left));
1492                         serializer.attribute("", ATTR_START_RECT_TOP,
1493                                 Integer.toString(startRect.top));
1494                         serializer.attribute("", ATTR_START_RECT_RIGHT,
1495                                 Integer.toString(startRect.right));
1496                         serializer.attribute("", ATTR_START_RECT_BOTTOM,
1497                                 Integer.toString(startRect.bottom));
1498 
1499                         final Rect endRect = ((EffectKenBurns)effect).getEndRect();
1500                         serializer.attribute("", ATTR_END_RECT_LEFT,
1501                                                 Integer.toString(endRect.left));
1502                         serializer.attribute("", ATTR_END_RECT_TOP,
1503                                                  Integer.toString(endRect.top));
1504                         serializer.attribute("", ATTR_END_RECT_RIGHT,
1505                                                Integer.toString(endRect.right));
1506                         serializer.attribute("", ATTR_END_RECT_BOTTOM,
1507                                 Integer.toString(endRect.bottom));
1508                         final MediaItem mItem = effect.getMediaItem();
1509                            if(((MediaImageItem)mItem).getGeneratedImageClip() != null) {
1510                                serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
1511                                        Boolean.toString(true));
1512                                serializer.attribute("", ATTR_GENERATED_IMAGE_CLIP,
1513                                      ((MediaImageItem)mItem).getGeneratedImageClip());
1514                             } else {
1515                                 serializer.attribute("", ATTR_IS_IMAGE_CLIP_GENERATED,
1516                                      Boolean.toString(false));
1517                          }
1518                     }
1519 
1520                     serializer.endTag("", TAG_EFFECT);
1521                 }
1522                 serializer.endTag("", TAG_EFFECTS);
1523             }
1524 
1525             serializer.endTag("", TAG_MEDIA_ITEM);
1526         }
1527         serializer.endTag("", TAG_MEDIA_ITEMS);
1528 
1529         serializer.startTag("", TAG_TRANSITIONS);
1530 
1531         for (Transition transition : mTransitions) {
1532             serializer.startTag("", TAG_TRANSITION);
1533             serializer.attribute("", ATTR_ID, transition.getId());
1534             serializer.attribute("", ATTR_TYPE, transition.getClass().getSimpleName());
1535             serializer.attribute("", ATTR_DURATION, Long.toString(transition.getDuration()));
1536             serializer.attribute("", ATTR_BEHAVIOR, Integer.toString(transition.getBehavior()));
1537             serializer.attribute("", ATTR_IS_TRANSITION_GENERATED,
1538                                     Boolean.toString(transition.isGenerated()));
1539             if (transition.isGenerated() == true) {
1540                 serializer.attribute("", ATTR_GENERATED_TRANSITION_CLIP, transition.mFilename);
1541             }
1542             final MediaItem afterMediaItem = transition.getAfterMediaItem();
1543             if (afterMediaItem != null) {
1544                 serializer.attribute("", ATTR_AFTER_MEDIA_ITEM_ID, afterMediaItem.getId());
1545             }
1546 
1547             final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
1548             if (beforeMediaItem != null) {
1549                 serializer.attribute("", ATTR_BEFORE_MEDIA_ITEM_ID, beforeMediaItem.getId());
1550             }
1551 
1552             if (transition instanceof TransitionSliding) {
1553                 serializer.attribute("", ATTR_DIRECTION,
1554                         Integer.toString(((TransitionSliding)transition).getDirection()));
1555             } else if (transition instanceof TransitionAlpha) {
1556                 TransitionAlpha ta = (TransitionAlpha)transition;
1557                 serializer.attribute("", ATTR_BLENDING,
1558                                      Integer.toString(ta.getBlendingPercent()));
1559                 serializer.attribute("", ATTR_INVERT,
1560                                                Boolean.toString(ta.isInvert()));
1561                 if (ta.getMaskFilename() != null) {
1562                     serializer.attribute("", ATTR_MASK, ta.getMaskFilename());
1563                 }
1564             }
1565             serializer.endTag("", TAG_TRANSITION);
1566         }
1567         serializer.endTag("", TAG_TRANSITIONS);
1568         serializer.startTag("", TAG_AUDIO_TRACKS);
1569         for (AudioTrack at : mAudioTracks) {
1570             serializer.startTag("", TAG_AUDIO_TRACK);
1571             serializer.attribute("", ATTR_ID, at.getId());
1572             serializer.attribute("", ATTR_FILENAME, at.getFilename());
1573             serializer.attribute("", ATTR_START_TIME, Long.toString(at.getStartTime()));
1574             serializer.attribute("", ATTR_BEGIN_TIME, Long.toString(at.getBoundaryBeginTime()));
1575             serializer.attribute("", ATTR_END_TIME, Long.toString(at.getBoundaryEndTime()));
1576             serializer.attribute("", ATTR_VOLUME, Integer.toString(at.getVolume()));
1577             serializer.attribute("", ATTR_DUCK_ENABLED,
1578                                        Boolean.toString(at.isDuckingEnabled()));
1579             serializer.attribute("", ATTR_DUCKED_TRACK_VOLUME,
1580                                    Integer.toString(at.getDuckedTrackVolume()));
1581             serializer.attribute("", ATTR_DUCK_THRESHOLD,
1582                                    Integer.toString(at.getDuckingThreshhold()));
1583             serializer.attribute("", ATTR_MUTED, Boolean.toString(at.isMuted()));
1584             serializer.attribute("", ATTR_LOOP, Boolean.toString(at.isLooping()));
1585             if (at.getAudioWaveformFilename() != null) {
1586                 serializer.attribute("", ATTR_AUDIO_WAVEFORM_FILENAME,
1587                         at.getAudioWaveformFilename());
1588             }
1589 
1590             serializer.endTag("", TAG_AUDIO_TRACK);
1591         }
1592         serializer.endTag("", TAG_AUDIO_TRACKS);
1593 
1594         serializer.endTag("", TAG_PROJECT);
1595         serializer.endDocument();
1596 
1597         /**
1598          *  Save the metadata XML file
1599          */
1600         final FileOutputStream out = new FileOutputStream(new File(getPath(),
1601                                                           PROJECT_FILENAME));
1602         out.write(writer.toString().getBytes());
1603         out.flush();
1604         out.close();
1605     }
1606 
1607     /*
1608      * {@inheritDoc}
1609      */
setAspectRatio(int aspectRatio)1610     public void setAspectRatio(int aspectRatio) {
1611         mAspectRatio = aspectRatio;
1612         /**
1613          *  Invalidate all transitions
1614          */
1615         mMANativeHelper.setGeneratePreview(true);
1616 
1617         for (Transition transition : mTransitions) {
1618             transition.invalidate();
1619         }
1620 
1621         final Iterator<MediaItem> it = mMediaItems.iterator();
1622 
1623         while (it.hasNext()) {
1624             final MediaItem t = it.next();
1625             List<Overlay> overlayList = t.getAllOverlays();
1626             for (Overlay overlay : overlayList) {
1627 
1628                 ((OverlayFrame)overlay).invalidateGeneratedFiles();
1629             }
1630         }
1631     }
1632 
1633     /*
1634      * {@inheritDoc}
1635      */
startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs, boolean loop, int callbackAfterFrameCount, PreviewProgressListener listener)1636     public void startPreview(SurfaceHolder surfaceHolder, long fromMs, long toMs,
1637                              boolean loop, int callbackAfterFrameCount,
1638                              PreviewProgressListener listener) {
1639 
1640         if (surfaceHolder == null) {
1641             throw new IllegalArgumentException();
1642         }
1643 
1644         final Surface surface = surfaceHolder.getSurface();
1645         if (surface == null) {
1646             throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1647         }
1648 
1649         if (surface.isValid() == false) {
1650             throw new IllegalStateException("Surface is not valid");
1651         }
1652 
1653         if (listener == null) {
1654             throw new IllegalArgumentException();
1655         }
1656 
1657         if (fromMs >= mDurationMs) {
1658             throw new IllegalArgumentException("Requested time not correct");
1659         }
1660 
1661         if (fromMs < 0) {
1662             throw new IllegalArgumentException("Requested time not correct");
1663         }
1664 
1665         boolean semAcquireDone = false;
1666         if (!mPreviewInProgress) {
1667             try{
1668                 semAcquireDone = lock(ENGINE_ACCESS_MAX_TIMEOUT_MS);
1669                 if (semAcquireDone == false) {
1670                     throw new IllegalStateException("Timeout waiting for semaphore");
1671                 }
1672 
1673                 if (mMANativeHelper == null) {
1674                     throw new IllegalStateException("The video editor is not initialized");
1675                 }
1676 
1677                 if (mMediaItems.size() > 0) {
1678                     mPreviewInProgress = true;
1679                     mMANativeHelper.previewStoryBoard(mMediaItems, mTransitions,
1680                                                       mAudioTracks, null);
1681                     mMANativeHelper.doPreview(surface, fromMs, toMs, loop,
1682                                      callbackAfterFrameCount, listener);
1683                 }
1684                 /**
1685                  *  Release The lock on complete by calling stopPreview
1686                  */
1687             } catch (InterruptedException ex) {
1688                 Log.w(TAG, "The thread was interrupted", new Throwable());
1689                 throw new IllegalStateException("The thread was interrupted");
1690             }
1691          } else {
1692             throw new IllegalStateException("Preview already in progress");
1693         }
1694     }
1695 
1696     /*
1697      * {@inheritDoc}
1698      */
stopPreview()1699     public long stopPreview() {
1700         long result = 0;
1701         if (mPreviewInProgress) {
1702             try {
1703                 result = mMANativeHelper.stopPreview();
1704                 /**
1705                  *  release on complete by calling stopPreview
1706                  */
1707                 } finally {
1708                     mPreviewInProgress = false;
1709                     unlock();
1710                 }
1711             return result;
1712         }
1713         else {
1714             return 0;
1715         }
1716     }
1717 
1718     /*
1719      * Remove transitions associated with the specified media item
1720      *
1721      * @param mediaItem The media item
1722      */
removeAdjacentTransitions(MediaItem mediaItem)1723     private void removeAdjacentTransitions(MediaItem mediaItem) {
1724         final Transition beginTransition = mediaItem.getBeginTransition();
1725         if (beginTransition != null) {
1726             if (beginTransition.getAfterMediaItem() != null) {
1727                 beginTransition.getAfterMediaItem().setEndTransition(null);
1728             }
1729             beginTransition.invalidate();
1730             mTransitions.remove(beginTransition);
1731         }
1732 
1733         final Transition endTransition = mediaItem.getEndTransition();
1734         if (endTransition != null) {
1735             if (endTransition.getBeforeMediaItem() != null) {
1736                 endTransition.getBeforeMediaItem().setBeginTransition(null);
1737             }
1738             endTransition.invalidate();
1739             mTransitions.remove(endTransition);
1740         }
1741 
1742         mediaItem.setBeginTransition(null);
1743         mediaItem.setEndTransition(null);
1744     }
1745 
1746     /**
1747      * Remove the transition before this media item
1748      *
1749      * @param index The media item index
1750      */
removeTransitionBefore(int index)1751     private void removeTransitionBefore(int index) {
1752         final MediaItem mediaItem = mMediaItems.get(index);
1753         final Iterator<Transition> it = mTransitions.iterator();
1754         while (it.hasNext()) {
1755             Transition t = it.next();
1756             if (t.getBeforeMediaItem() == mediaItem) {
1757                 mMANativeHelper.setGeneratePreview(true);
1758                 it.remove();
1759                 t.invalidate();
1760                 mediaItem.setBeginTransition(null);
1761                 if (index > 0) {
1762                     mMediaItems.get(index - 1).setEndTransition(null);
1763                 }
1764                 break;
1765             }
1766         }
1767     }
1768 
1769     /**
1770      * Remove the transition after this media item
1771      *
1772      * @param mediaItem The media item
1773      */
removeTransitionAfter(int index)1774     private void removeTransitionAfter(int index) {
1775         final MediaItem mediaItem = mMediaItems.get(index);
1776         final Iterator<Transition> it = mTransitions.iterator();
1777         while (it.hasNext()) {
1778             Transition t = it.next();
1779             if (t.getAfterMediaItem() == mediaItem) {
1780                 mMANativeHelper.setGeneratePreview(true);
1781                 it.remove();
1782                 t.invalidate();
1783                 mediaItem.setEndTransition(null);
1784                 /**
1785                  *  Invalidate the reference in the next media item
1786                  */
1787                 if (index < mMediaItems.size() - 1) {
1788                     mMediaItems.get(index + 1).setBeginTransition(null);
1789                 }
1790                 break;
1791             }
1792         }
1793     }
1794 
1795     /**
1796      * Compute the duration
1797      */
computeTimelineDuration()1798     private void computeTimelineDuration() {
1799         mDurationMs = 0;
1800         final int mediaItemsCount = mMediaItems.size();
1801         for (int i = 0; i < mediaItemsCount; i++) {
1802             final MediaItem mediaItem = mMediaItems.get(i);
1803             mDurationMs += mediaItem.getTimelineDuration();
1804             if (mediaItem.getEndTransition() != null) {
1805                 if (i < mediaItemsCount - 1) {
1806                     mDurationMs -= mediaItem.getEndTransition().getDuration();
1807                 }
1808             }
1809         }
1810     }
1811 
1812     /*
1813      * Generate the project thumbnail
1814      */
generateProjectThumbnail()1815     private void generateProjectThumbnail() {
1816         /*
1817          * If a thumbnail already exists, then delete it first
1818          */
1819         if ((new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).exists()) {
1820             (new File(mProjectPath + "/" + THUMBNAIL_FILENAME)).delete();
1821         }
1822         /*
1823          * Generate a new thumbnail for the project from first media Item
1824          */
1825         if (mMediaItems.size() > 0) {
1826             MediaItem mI = mMediaItems.get(0);
1827             /*
1828              * Keep aspect ratio of the image
1829              */
1830             int height = 480;
1831             int width = mI.getWidth() * height / mI.getHeight();
1832 
1833             Bitmap projectBitmap = null;
1834             String filename = mI.getFilename();
1835             if (mI instanceof MediaVideoItem) {
1836                 MediaMetadataRetriever retriever = new MediaMetadataRetriever();
1837                 retriever.setDataSource(filename);
1838                 Bitmap bitmap = retriever.getFrameAtTime();
1839                 retriever.release();
1840                 retriever = null;
1841                 if (bitmap == null) {
1842                     String msg = "Thumbnail extraction from " +
1843                                     filename + " failed";
1844                     throw new IllegalArgumentException(msg);
1845                 }
1846                 // Resize the thumbnail to the target size
1847                 projectBitmap =
1848                     Bitmap.createScaledBitmap(bitmap, width, height, true);
1849             } else {
1850                 try {
1851                     projectBitmap = mI.getThumbnail(width, height, 500);
1852                 } catch (IllegalArgumentException e) {
1853                     String msg = "Project thumbnail extraction from " +
1854                                     filename + " failed";
1855                     throw new IllegalArgumentException(msg);
1856                 } catch (IOException e) {
1857                     String msg = "IO Error creating project thumbnail";
1858                     throw new IllegalArgumentException(msg);
1859                 }
1860             }
1861 
1862             try {
1863                 FileOutputStream stream = new FileOutputStream(mProjectPath + "/"
1864                                                           + THUMBNAIL_FILENAME);
1865                 projectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
1866                 stream.flush();
1867                 stream.close();
1868             } catch (IOException e) {
1869                 throw new IllegalArgumentException ("Error creating project thumbnail");
1870             } finally {
1871                 projectBitmap.recycle();
1872             }
1873         }
1874     }
1875 
1876     /**
1877      * Clears the preview surface
1878      *
1879      * @param surfaceHolder SurfaceHolder where the preview is rendered
1880      * and needs to be cleared.
1881      */
clearSurface(SurfaceHolder surfaceHolder)1882     public void clearSurface(SurfaceHolder surfaceHolder) {
1883         if (surfaceHolder == null) {
1884             throw new IllegalArgumentException("Invalid surface holder");
1885         }
1886 
1887         final Surface surface = surfaceHolder.getSurface();
1888         if (surface == null) {
1889             throw new IllegalArgumentException("Surface could not be retrieved from surface holder");
1890         }
1891 
1892         if (surface.isValid() == false) {
1893             throw new IllegalStateException("Surface is not valid");
1894         }
1895 
1896         if (mMANativeHelper != null) {
1897             mMANativeHelper.clearPreviewSurface(surface);
1898         } else {
1899             Log.w(TAG, "Native helper was not ready!");
1900         }
1901     }
1902 
1903     /**
1904      * Grab the semaphore which arbitrates access to the editor
1905      *
1906      * @throws InterruptedException
1907      */
lock()1908     private void lock() throws InterruptedException {
1909         if (Log.isLoggable(TAG, Log.DEBUG)) {
1910             Log.d(TAG, "lock: grabbing semaphore", new Throwable());
1911         }
1912         mLock.acquire();
1913         if (Log.isLoggable(TAG, Log.DEBUG)) {
1914             Log.d(TAG, "lock: grabbed semaphore");
1915         }
1916     }
1917 
1918     /**
1919      * Tries to grab the semaphore with a specified time out which arbitrates access to the editor
1920      *
1921      * @param timeoutMs time out in ms.
1922      *
1923      * @return true if the semaphore is acquired, false otherwise
1924      * @throws InterruptedException
1925      */
lock(long timeoutMs)1926     private boolean lock(long timeoutMs) throws InterruptedException {
1927         if (Log.isLoggable(TAG, Log.DEBUG)) {
1928             Log.d(TAG, "lock: grabbing semaphore with timeout " + timeoutMs, new Throwable());
1929         }
1930 
1931         boolean acquireSem = mLock.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
1932         if (Log.isLoggable(TAG, Log.DEBUG)) {
1933             Log.d(TAG, "lock: grabbed semaphore status " + acquireSem);
1934         }
1935 
1936         return acquireSem;
1937     }
1938 
1939     /**
1940      * Release the semaphore which arbitrates access to the editor
1941      */
unlock()1942     private void unlock() {
1943         if (Log.isLoggable(TAG, Log.DEBUG)) {
1944             Log.d(TAG, "unlock: releasing semaphore");
1945         }
1946         mLock.release();
1947     }
1948 
1949     /**
1950      * Dumps the heap memory usage information to file
1951      */
dumpHeap(String filename)1952     private static void dumpHeap (String filename) throws Exception {
1953         /* Cleanup as much as possible before dump
1954          */
1955         System.gc();
1956         System.runFinalization();
1957         Thread.sleep(1000);
1958         String state = Environment.getExternalStorageState();
1959         if (Environment.MEDIA_MOUNTED.equals(state)) {
1960             String extDir =
1961              Environment.getExternalStorageDirectory().toString();
1962 
1963             /* If dump file already exists, then delete it first
1964             */
1965             if ((new File(extDir + "/" + filename + ".dump")).exists()) {
1966                 (new File(extDir + "/" + filename + ".dump")).delete();
1967             }
1968             /* Dump native heap
1969             */
1970             FileOutputStream ost =
1971              new FileOutputStream(extDir + "/" + filename + ".dump");
1972             Debug.dumpNativeHeap(ost.getFD());
1973             ost.close();
1974         }
1975     }
1976 }
1977