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
18package android.media.videoeditor;
19
20import java.io.File;
21import java.io.FileInputStream;
22import java.io.FileNotFoundException;
23import java.io.FileOutputStream;
24import java.io.IOException;
25import java.io.StringWriter;
26import java.util.ArrayList;
27import java.util.Iterator;
28import java.util.List;
29import java.util.Map;
30import java.util.concurrent.Semaphore;
31import java.util.concurrent.TimeUnit;
32
33import org.xmlpull.v1.XmlPullParser;
34import org.xmlpull.v1.XmlPullParserException;
35import org.xmlpull.v1.XmlSerializer;
36
37import android.graphics.Bitmap;
38import android.graphics.Rect;
39import android.media.videoeditor.MediaImageItem;
40import android.media.videoeditor.MediaItem;
41import android.media.MediaMetadataRetriever;
42import android.util.Log;
43import android.util.Xml;
44import android.view.Surface;
45import android.view.SurfaceHolder;
46import android.os.Debug;
47import android.os.SystemProperties;
48import android.os.Environment;
49
50/**
51 * The VideoEditor implementation {@hide}
52 */
53public 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     */
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     */
181    MediaArtistNativeHelper getNativeContext() {
182        return mMANativeHelper;
183    }
184
185    /*
186     * {@inheritDoc}
187     */
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     */
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     */
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     */
329    public void cancelExport(String filename) {
330        if (mMANativeHelper != null && filename != null) {
331            mMANativeHelper.stop(filename);
332        }
333    }
334
335    /*
336     * {@inheritDoc}
337     */
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     */
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     */
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     */
510    public List<AudioTrack> getAllAudioTracks() {
511        return mAudioTracks;
512    }
513
514    /*
515     * {@inheritDoc}
516     */
517    public List<MediaItem> getAllMediaItems() {
518        return mMediaItems;
519    }
520
521    /*
522     * {@inheritDoc}
523     */
524    public List<Transition> getAllTransitions() {
525        return mTransitions;
526    }
527
528    /*
529     * {@inheritDoc}
530     */
531    public int getAspectRatio() {
532        return mAspectRatio;
533    }
534
535    /*
536     * {@inheritDoc}
537     */
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     */
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     */
562    void updateTimelineDuration() {
563        computeTimelineDuration();
564    }
565
566    /*
567     * {@inheritDoc}
568     */
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     */
581    public String getPath() {
582        return mProjectPath;
583    }
584
585    /*
586     * {@inheritDoc}
587     */
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     */
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     */
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     */
670    public synchronized void moveAudioTrack(String audioTrackId, String afterAudioTrackId) {
671        throw new IllegalStateException("Not supported");
672    }
673
674    /*
675     * {@inheritDoc}
676     */
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     */
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     */
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     */
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     */
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
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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