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