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
17package com.android.videoeditor;
18
19import java.util.List;
20
21import android.app.Activity;
22import android.content.Context;
23import android.content.Intent;
24import android.graphics.Bitmap;
25import android.media.AudioManager;
26import android.media.videoeditor.AudioTrack;
27import android.media.videoeditor.MediaItem;
28import android.media.videoeditor.MediaVideoItem;
29import android.media.videoeditor.Transition;
30import android.net.Uri;
31import android.os.Bundle;
32import android.util.Log;
33import android.widget.Toast;
34
35import com.android.videoeditor.service.ApiService;
36import com.android.videoeditor.service.ApiServiceListener;
37import com.android.videoeditor.service.MovieAudioTrack;
38import com.android.videoeditor.service.MovieEffect;
39import com.android.videoeditor.service.MovieMediaItem;
40import com.android.videoeditor.service.MovieOverlay;
41import com.android.videoeditor.service.MovieTransition;
42import com.android.videoeditor.service.VideoEditorProject;
43import com.android.videoeditor.widgets.AudioTrackLinearLayout;
44import com.android.videoeditor.widgets.MediaLinearLayout;
45import com.android.videoeditor.widgets.OverlayLinearLayout;
46
47/**
48 * This activity handles callbacks from the service and manages the project
49 */
50public abstract class VideoEditorBaseActivity extends Activity {
51    // Logging
52    private static final String TAG = "VideoEditorBase";
53
54    protected static final int DIALOG_DELETE_BAD_PROJECT_ID = 100;
55
56    // State keys
57    private static final String STATE_PROJECT_PATH = "path";
58    private static final String STATE_EXPORT_FILENAME = "export_filename";
59
60    // Dialog parameters
61    protected static final String PARAM_PROJECT_PATH = "path";
62
63    // Instance variables
64    private final ServiceListener mServiceListener = new ServiceListener();
65    protected String mProjectPath;
66    protected VideoEditorProject mProject;
67    protected String mPendingExportFilename;
68    private boolean mProjectEditState;
69
70    /**
71     * The service listener
72     */
73    private class ServiceListener extends ApiServiceListener {
74
75        @Override
76        public void onProjectEditState(String projectPath, boolean projectEdited) {
77            // Check if the VideoEditor is the one we are expecting
78            if (!projectPath.equals(mProjectPath)) {
79                return;
80            }
81
82            if (mProjectEditState != projectEdited) {
83                mProjectEditState = projectEdited;
84                onProjectEditStateChange(projectEdited);
85            }
86        }
87
88        @Override
89        public void onVideoEditorCreated(String projectPath, VideoEditorProject project,
90                List<MediaItem> mediaItems, List<AudioTrack> audioTracks, Exception exception) {
91            // Check if the VideoEditor is the one we are expecting
92            if (!projectPath.equals(mProjectPath)) {
93                return;
94            }
95
96            // Check if an error occurred
97            if (exception != null) {
98                // Invalidate the project path
99                mProjectPath = null;
100
101                enterDisabledState(R.string.editor_no_project);
102                // Display an error
103                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_create_error,
104                        Toast.LENGTH_LONG).show();
105            } else {
106                enterReadyState();
107
108                mProject = project;
109                initializeFromProject(true);
110            }
111        }
112
113        @Override
114        public void onVideoEditorLoaded(String projectPath, VideoEditorProject project,
115                List<MediaItem> mediaItems, List<AudioTrack> audioTracks, Exception exception) {
116            if (!projectPath.equals(mProjectPath)) {
117                return;
118            }
119
120            // Check if an error occurred
121            if (exception != null || project == null) {
122                mProjectPath = null;
123
124                enterDisabledState(R.string.editor_no_project);
125
126                final Bundle bundle = new Bundle();
127                bundle.putString(VideoEditorActivity.PARAM_PROJECT_PATH, projectPath);
128                showDialog(DIALOG_DELETE_BAD_PROJECT_ID, bundle);
129            } else {
130                // The project may be loaded already. This can happen when we
131                // create a new project
132                if (mProject == null) {
133                    mProject = project;
134                    initializeFromProject(true);
135                }
136            }
137        }
138
139        @Override
140        public void onVideoEditorAspectRatioSet(String projectPath, int aspectRatio,
141                Exception exception) {
142            // Check if the VideoEditor is the one we are expecting
143            if (!projectPath.equals(mProjectPath)) {
144                return;
145            }
146
147            if (mProject == null) {
148                return;
149            }
150
151            if (exception != null) {
152                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_aspect_ratio_error,
153                        Toast.LENGTH_LONG).show();
154            } else {
155                // The aspect ratio has changed
156                setAspectRatio(aspectRatio);
157            }
158        }
159
160        @Override
161        public void onVideoEditorThemeApplied(String projectPath, String theme,
162                Exception exception) {
163            // Check if the VideoEditor is the one we are expecting
164            if (!projectPath.equals(mProjectPath)) {
165                return;
166            }
167
168            if (mProject == null) {
169                return;
170            }
171
172            if (exception != null) {
173                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_apply_theme_error,
174                        Toast.LENGTH_LONG).show();
175            } else {
176                getMediaLayout().addMediaItems(mProject.getMediaItems());
177                getOverlayLayout().addMediaItems(mProject.getMediaItems());
178                getAudioTrackLayout().addAudioTracks(mProject.getAudioTracks());
179
180                updateTimelineDuration();
181            }
182        }
183
184        @Override
185        public void onVideoEditorGeneratePreviewProgress(String projectPath, String className,
186                String itemId, int action, int progress) {
187            // Check if the VideoEditor is the one we are expecting
188            if (!projectPath.equals(mProjectPath)) {
189                return;
190            }
191
192            if (mProject == null) {
193                return;
194            }
195
196            if (Log.isLoggable(TAG, Log.VERBOSE)) {
197                Log.v(TAG, "onVideoEditorGeneratePreviewProgress: " + className + " " + progress);
198            }
199
200            if (className == null) { // Last callback (after all items are generated)
201                if (action == ApiService.ACTION_UPDATE_FRAME) {
202                    showPreviewFrame();
203                }
204            } else if (MediaItem.class.getCanonicalName().equals(className)) {
205                getMediaLayout().onGeneratePreviewMediaItemProgress(itemId, action, progress);
206            } else if (Transition.class.getCanonicalName().equals(className)) {
207                getMediaLayout().onGeneratePreviewTransitionProgress(itemId, action, progress);
208            } else if (AudioTrack.class.getCanonicalName().equals(className)) {
209                getAudioTrackLayout().onGeneratePreviewProgress(itemId, action, progress);
210            } else {
211                Log.w(TAG, "Unsupported storyboard item type: " + className);
212            }
213        }
214
215        @Override
216        public void onVideoEditorExportProgress(String projectPath, String filename,
217                int progress) {
218            // Check if the VideoEditor is the one we are expecting
219            if (!projectPath.equals(mProjectPath)) {
220                return;
221            }
222
223            if (mProject == null) {
224                return;
225            }
226
227            if (!filename.equals(mPendingExportFilename)) {
228                return;
229            }
230
231            // Update the export progress
232            onExportProgress(progress);
233        }
234
235        @Override
236        public void onVideoEditorExportComplete(String projectPath, String filename,
237                Exception exception, boolean cancelled) {
238            // Check if the VideoEditor is the one we are expecting
239            if (!projectPath.equals(mProjectPath)) {
240                return;
241            }
242
243            if (mProject == null) {
244                return;
245            }
246
247            if (!filename.equals(mPendingExportFilename)) {
248                return;
249            }
250
251            onExportComplete();
252
253            mPendingExportFilename = null;
254
255            if (exception != null) {
256                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_export_error,
257                        Toast.LENGTH_LONG).show();
258            }
259        }
260
261        @Override
262        public void onVideoEditorSaved(String projectPath, Exception exception) {
263            // Check if the VideoEditor is the one we are expecting
264            if (!projectPath.equals(mProjectPath)) {
265                return;
266            }
267
268            if (exception != null) {
269                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_saved_error,
270                        Toast.LENGTH_LONG).show();
271            }
272        }
273
274        @Override
275        public void onVideoEditorReleased(String projectPath, Exception exception) {
276            if (exception != null) {
277                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_release_error,
278                        Toast.LENGTH_LONG).show();
279            }
280        }
281
282        @Override
283        public void onVideoEditorDeleted(String projectPath, Exception exception) {
284            if (exception != null) {
285                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_delete_error,
286                        Toast.LENGTH_LONG).show();
287            }
288        }
289
290        @Override
291        public void onMediaItemAdded(String projectPath, String mediaItemId,
292                MovieMediaItem mediaItem, String afterMediaItemId, Class<?> mediaItemClass,
293                Integer newAspectRatio, Exception exception) {
294            // Check if the VideoEditor is the one we are expecting.
295            if (!projectPath.equals(mProjectPath) || mProject == null) {
296                return;
297            }
298
299            if (exception != null) {
300                if (mediaItemClass.getCanonicalName().equals(
301                        MediaVideoItem.class.getCanonicalName())) {
302                    Toast.makeText(VideoEditorBaseActivity.this,
303                            R.string.editor_add_video_clip_error, Toast.LENGTH_LONG).show();
304                } else {
305                    Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_add_image_error,
306                            Toast.LENGTH_LONG).show();
307                }
308            } else {
309                getMediaLayout().insertMediaItem(mediaItem, afterMediaItemId);
310                getOverlayLayout().insertMediaItem(mediaItem, afterMediaItemId);
311
312                if (newAspectRatio != null) {
313                    // The aspect ratio has changed
314                    setAspectRatio(newAspectRatio);
315                }
316
317                updateTimelineDuration();
318            }
319        }
320
321        @Override
322        public void onMediaLoaded(String projectPath, Uri mediaIUri, String mimeType,
323                String filename, Exception exception) {
324            // Check if the VideoEditor is the one we are expecting
325            if (!projectPath.equals(mProjectPath)) {
326                return;
327            }
328
329            if (mProject == null) {
330                return;
331            }
332            if (exception != null) {
333                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_media_load_error,
334                            Toast.LENGTH_LONG).show();
335            } else {
336                // Update the status of the downloaded item in the user interface
337            }
338        }
339
340        @Override
341        public void onMediaItemMoved(String projectPath, String mediaItemId,
342                String afterMediaItemId, Exception exception) {
343            // Check if the VideoEditor is the one we are expecting
344            if (!projectPath.equals(mProjectPath)) {
345                return;
346            }
347
348            if (mProject == null) {
349                return;
350            }
351
352            if (exception != null) {
353                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_move_media_item_error,
354                            Toast.LENGTH_LONG).show();
355            } else {
356                // Update the entire timeline
357                getMediaLayout().addMediaItems(mProject.getMediaItems());
358                getOverlayLayout().addMediaItems(mProject.getMediaItems());
359
360                updateTimelineDuration();
361            }
362        }
363
364        @Override
365        public void onMediaItemRemoved(String projectPath, String mediaItemId,
366                MovieTransition transition, Exception exception) {
367            // Check if the VideoEditor is the one we are expecting
368            if (!projectPath.equals(mProjectPath)) {
369                return;
370            }
371
372            if (mProject == null) {
373                return;
374            }
375
376            if (exception != null) {
377                Toast.makeText(VideoEditorBaseActivity.this,
378                        R.string.editor_remove_media_item_error, Toast.LENGTH_LONG).show();
379            } else {
380                // Remove the media item and bounding transitions
381                getMediaLayout().removeMediaItem(mediaItemId, transition);
382                getOverlayLayout().removeMediaItem(mediaItemId);
383
384                updateTimelineDuration();
385            }
386        }
387
388        @Override
389        public void onMediaItemRenderingModeSet(String projectPath, String mediaItemId,
390                int renderingMode, Exception exception) {
391            // Check if the VideoEditor is the one we are expecting
392            if (!projectPath.equals(mProjectPath)) {
393                return;
394            }
395
396            if (mProject == null) {
397                return;
398            }
399
400            if (exception != null) {
401                Toast.makeText(VideoEditorBaseActivity.this,
402                        R.string.editor_set_rendering_mode_error, Toast.LENGTH_LONG).show();
403            }
404        }
405
406        @Override
407        public void onMediaItemDurationSet(String projectPath, String mediaItemId,
408                long durationMs, Exception exception) {
409            // Check if the VideoEditor is the one we are expecting
410            if (!projectPath.equals(mProjectPath)) {
411                return;
412            }
413
414            if (mProject == null) {
415                return;
416            }
417
418            if (exception != null) {
419                Toast.makeText(VideoEditorBaseActivity.this,
420                        R.string.editor_set_media_item_duration_error, Toast.LENGTH_LONG).show();
421            } else {
422                final MovieMediaItem mediaItem = mProject.getMediaItem(mediaItemId);
423                // Update the media item
424                getMediaLayout().updateMediaItem(mediaItem);
425                getOverlayLayout().updateMediaItem(mediaItem);
426
427                updateTimelineDuration();
428            }
429        }
430
431        @Override
432        public void onMediaItemBoundariesSet(String projectPath, String mediaItemId,
433                long beginBoundaryMs, long endBoundaryMs, Exception exception) {
434            // Check if the VideoEditor is the one we are expecting
435            if (!projectPath.equals(mProjectPath)) {
436                return;
437            }
438
439            if (mProject == null) {
440                return;
441            }
442
443            if (exception != null) {
444                Toast.makeText(VideoEditorBaseActivity.this,
445                        R.string.editor_set_media_item_boundaries_error, Toast.LENGTH_LONG).show();
446            } else {
447                final MovieMediaItem mediaItem = mProject.getMediaItem(mediaItemId);
448                getMediaLayout().updateMediaItem(mediaItem);
449                getOverlayLayout().updateMediaItem(mediaItem);
450
451                // Place the cursor at the beginning of the trimmed media item
452                //movePlayhead(mProject.getMediaItemBeginTime(mediaItemId));
453                updateTimelineDuration();
454            }
455        }
456
457        @Override
458        public boolean onMediaItemThumbnail(String projectPath, String mediaItemId,
459                Bitmap thumbnail, int index, int token, Exception exception) {
460            // Check if the VideoEditor is the one we are expecting
461            if (!projectPath.equals(mProjectPath)) {
462                return false;
463            }
464
465            if (mProject == null) {
466                return false;
467            }
468
469            if (exception != null) {
470                return false;
471            } else {
472                return getMediaLayout().setMediaItemThumbnail(
473                        mediaItemId, thumbnail, index, token);
474            }
475        }
476
477        @Override
478        public void onTransitionInserted(String projectPath, MovieTransition transition,
479                String afterMediaId, Exception exception) {
480            // Check if the VideoEditor is the one we are expecting
481            if (!projectPath.equals(mProjectPath)) {
482                return;
483            }
484
485            if (mProject == null) {
486                return;
487            }
488
489            if (exception != null) {
490                 Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_add_transition_error,
491                            Toast.LENGTH_LONG).show();
492            } else {
493                getMediaLayout().addTransition(transition, afterMediaId);
494
495                updateTimelineDuration();
496            }
497        }
498
499        @Override
500        public void onTransitionRemoved(String projectPath, String transitionId,
501                Exception exception) {
502            // Check if the VideoEditor is the one we are expecting
503            if (!projectPath.equals(mProjectPath)) {
504                return;
505            }
506
507            if (mProject == null) {
508                return;
509            }
510
511            if (exception != null) {
512                Toast.makeText(VideoEditorBaseActivity.this,
513                        R.string.editor_remove_transition_error, Toast.LENGTH_LONG).show();
514            } else {
515                getMediaLayout().removeTransition(transitionId);
516
517                updateTimelineDuration();
518            }
519        }
520
521        @Override
522        public void onTransitionDurationSet(String projectPath, String transitionId,
523                long durationMs, Exception exception) {
524            // Check if the VideoEditor is the one we are expecting
525            if (!projectPath.equals(mProjectPath)) {
526                return;
527            }
528
529            if (mProject == null) {
530                return;
531            }
532
533            if (exception != null) {
534                Toast.makeText(VideoEditorBaseActivity.this,
535                        R.string.editor_set_transition_duration_error, Toast.LENGTH_LONG).show();
536            } else {
537                getMediaLayout().updateTransition(transitionId);
538                getOverlayLayout().refresh();
539
540                updateTimelineDuration();
541            }
542        }
543
544        @Override
545        public boolean onTransitionThumbnails(String projectPath, String transitionId,
546                Bitmap[] thumbnails, Exception exception) {
547            // Check if the VideoEditor is the one we are expecting
548            if (!projectPath.equals(mProjectPath)) {
549                return false;
550            }
551
552            if (mProject == null) {
553                return false;
554            }
555
556            if (exception != null) {
557                return false;
558            } else {
559                return getMediaLayout().setTransitionThumbnails(transitionId, thumbnails);
560            }
561        }
562
563        @Override
564        public void onOverlayAdded(String projectPath, MovieOverlay overlay,
565                String mediaItemId, Exception exception) {
566            // Check if the VideoEditor is the one we are expecting
567            if (!projectPath.equals(mProjectPath)) {
568                return;
569            }
570
571            if (mProject == null) {
572                return;
573            }
574
575            if (exception != null) {
576                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_add_overlay_error,
577                            Toast.LENGTH_LONG).show();
578            } else {
579                getMediaLayout().invalidateActionBar();
580                getOverlayLayout().addOverlay(mediaItemId, overlay);
581            }
582        }
583
584        @Override
585        public void onOverlayRemoved(String projectPath, String overlayId,
586                String mediaItemId, Exception exception) {
587            // Check if the VideoEditor is the one we are expecting
588            if (!projectPath.equals(mProjectPath)) {
589                return;
590            }
591
592            if (mProject == null) {
593                return;
594            }
595
596            if (exception != null) {
597                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_remove_overlay_error,
598                            Toast.LENGTH_LONG).show();
599            } else {
600                getOverlayLayout().removeOverlay(mediaItemId, overlayId);
601            }
602        }
603
604        @Override
605        public void onOverlayStartTimeSet(String projectPath, String overlayId,
606                String mediaItemId, long startTimeMs, Exception exception) {
607            // Check if the VideoEditor is the one we are expecting
608            if (!projectPath.equals(mProjectPath)) {
609                return;
610            }
611
612            if (mProject == null) {
613                return;
614            }
615
616            if (exception != null) {
617                Toast.makeText(VideoEditorBaseActivity.this,
618                        R.string.editor_set_start_time_overlay_error, Toast.LENGTH_LONG).show();
619            }
620        }
621
622        @Override
623        public void onOverlayDurationSet(String projectPath, String overlayId,
624                String mediaItemId, long durationMs, Exception exception) {
625            // Check if the VideoEditor is the one we are expecting
626            if (!projectPath.equals(mProjectPath)) {
627                return;
628            }
629
630            if (mProject == null) {
631                return;
632            }
633
634            if (exception != null) {
635                Toast.makeText(VideoEditorBaseActivity.this,
636                        R.string.editor_set_duration_overlay_error, Toast.LENGTH_LONG).show();
637            }
638        }
639
640        @Override
641        public void onOverlayUserAttributesSet(String projectPath, String overlayId,
642                String mediaItemId, Bundle userAttributes, Exception exception) {
643            // Check if the VideoEditor is the one we are expecting
644            if (!projectPath.equals(mProjectPath)) {
645                return;
646            }
647
648            if (mProject == null) {
649                return;
650            }
651
652            if (exception != null) {
653                Toast.makeText(VideoEditorBaseActivity.this,
654                        R.string.editor_set_user_attributes_overlay_error,
655                        Toast.LENGTH_LONG).show();
656            } else {
657                getOverlayLayout().updateOverlayAttributes(mediaItemId, overlayId, userAttributes);
658            }
659        }
660
661        @Override
662        public void onEffectAdded(String projectPath, MovieEffect effect, String mediaItemId,
663                Exception exception) {
664            // Check if the VideoEditor is the one we are expecting
665            if (!projectPath.equals(mProjectPath)) {
666                return;
667            }
668
669            if (mProject == null) {
670                return;
671            }
672
673            if (exception != null) {
674                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_add_effect_error,
675                            Toast.LENGTH_LONG).show();
676            } else {
677                getMediaLayout().updateMediaItem(mProject.getMediaItem(mediaItemId));
678            }
679        }
680
681        @Override
682        public void onEffectRemoved(String projectPath, String effectId, String mediaItemId,
683                Exception exception) {
684            // Check if the VideoEditor is the one we are expecting
685            if (!projectPath.equals(mProjectPath)) {
686                return;
687            }
688
689            if (mProject == null) {
690                return;
691            }
692
693            if (exception != null) {
694                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_remove_effect_error,
695                            Toast.LENGTH_LONG).show();
696            } else {
697                // Remove the effect
698                getMediaLayout().updateMediaItem(mProject.getMediaItem(mediaItemId));
699            }
700        }
701
702        @Override
703        public void onAudioTrackAdded(String projectPath, MovieAudioTrack audioTrack,
704                Exception exception) {
705            // Check if the VideoEditor is the one we are expecting
706            if (!projectPath.equals(mProjectPath)) {
707                return;
708            }
709
710            if (mProject == null) {
711                return;
712            }
713
714            if (exception != null) {
715                Toast.makeText(VideoEditorBaseActivity.this, R.string.editor_add_audio_track_error,
716                            Toast.LENGTH_LONG).show();
717            } else {
718                getAudioTrackLayout().addAudioTrack(audioTrack);
719            }
720        }
721
722        @Override
723        public void onAudioTrackRemoved(String projectPath, String audioTrackId,
724                Exception exception) {
725            // Check if the VideoEditor is the one we are expecting
726            if (!projectPath.equals(mProjectPath)) {
727                return;
728            }
729
730            if (mProject == null) {
731                return;
732            }
733
734            if (exception != null) {
735                Toast.makeText(VideoEditorBaseActivity.this,
736                        R.string.editor_remove_audio_track_error, Toast.LENGTH_LONG).show();
737            } else {
738                getAudioTrackLayout().removeAudioTrack(audioTrackId);
739            }
740        }
741
742        @Override
743        public void onAudioTrackBoundariesSet(String projectPath, String audioTrackId,
744                long beginBoundaryMs, long endBoundaryMs, Exception exception) {
745            // Check if the VideoEditor is the one we are expecting
746            if (!projectPath.equals(mProjectPath)) {
747                return;
748            }
749
750            if (mProject == null) {
751                return;
752            }
753
754            if (exception != null) {
755                Toast.makeText(VideoEditorBaseActivity.this,
756                        R.string.editor_set_audio_track_boundaries_error,
757                        Toast.LENGTH_LONG).show();
758            } else {
759                getAudioTrackLayout().updateAudioTrack(audioTrackId);
760            }
761        }
762
763        @Override
764        public void onAudioTrackExtractAudioWaveformProgress(String projectPath,
765                String audioTrackId, int progress) {
766            // Check if the VideoEditor is the one we are expecting
767            if (!projectPath.equals(mProjectPath)) {
768                return;
769            }
770
771            if (mProject == null) {
772                return;
773            }
774
775            getAudioTrackLayout().setWaveformExtractionProgress(audioTrackId, progress);
776        }
777
778        @Override
779        public void onAudioTrackExtractAudioWaveformComplete(String projectPath,
780                String audioTrackId, Exception exception) {
781            // Check if the VideoEditor is the one we are expecting
782            if (!projectPath.equals(mProjectPath)) {
783                return;
784            }
785
786            if (mProject == null) {
787                return;
788            }
789
790            if (exception == null) {
791                getAudioTrackLayout().setWaveformExtractionComplete(audioTrackId);
792            }
793        }
794    }
795
796    @Override
797    public void onCreate(Bundle savedInstanceState) {
798        super.onCreate(savedInstanceState);
799        setContentView(R.layout.video_editor);
800
801        if (savedInstanceState != null) {
802            mProjectPath = savedInstanceState.getString(STATE_PROJECT_PATH);
803            mPendingExportFilename = savedInstanceState.getString(STATE_EXPORT_FILENAME);
804        } else {
805            final Intent intent = getIntent();
806            mProjectPath = intent.getStringExtra(ProjectsActivity.PARAM_OPEN_PROJECT_PATH);
807            if (Intent.ACTION_INSERT.equals(intent.getAction())) {
808                ApiService.createVideoEditor(this, mProjectPath,
809                        intent.getStringExtra(ProjectsActivity.PARAM_CREATE_PROJECT_NAME),
810                        new String[0], new String[0], null);
811            }
812        }
813    }
814
815    @Override
816    public void onResume() {
817        super.onResume();
818
819        mProjectEditState = ApiService.isProjectBeingEdited(mProjectPath);
820        onProjectEditStateChange(mProjectEditState);
821
822        ApiService.registerListener(mServiceListener);
823
824        // Check if we need to load the project
825        if (mProjectPath != null) {
826            if (mProject == null) {
827                // We need to load the project
828                ApiService.loadVideoEditor(this, mProjectPath);
829                enterTransitionalState(R.string.editor_loading_project);
830            } else { // The project is already loaded
831                initializeFromProject(false);
832            }
833        } else {
834            enterDisabledState(R.string.editor_no_project);
835        }
836
837        // Request the audio focus so that other apps can pause playback.
838        final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
839        audioManager.requestAudioFocus(null, AudioManager.STREAM_MUSIC,
840                AudioManager.AUDIOFOCUS_GAIN);
841    }
842
843    @Override
844    public void onPause() {
845        super.onPause();
846        ApiService.unregisterListener(mServiceListener);
847
848        if (mProject != null) {
849            // Mark the project as clean. If/when we resume the activity
850            // we can check this flag in order to refresh the UI for changes
851            // which may had occurred when the activity was paused.
852            mProject.setClean(true);
853            // Save the contents of the current project
854            ApiService.saveVideoEditor(this, mProjectPath);
855        }
856
857        // Release the audio focus
858        final AudioManager audioManager = (AudioManager)getSystemService(Context.AUDIO_SERVICE);
859        audioManager.abandonAudioFocus(null);
860    }
861
862    @Override
863    public void onDestroy() {
864        super.onDestroy();
865
866        // If we have an active project release the VideoEditor
867        if (mProjectPath != null) {
868            if (!isChangingConfigurations()) {
869                ApiService.releaseVideoEditor(this, mProjectPath);
870            }
871
872            mProjectPath = null;
873        }
874    }
875
876    @Override
877    public void onSaveInstanceState(Bundle outState) {
878        super.onSaveInstanceState(outState);
879
880        outState.putString(STATE_PROJECT_PATH, mProjectPath);
881        outState.putString(STATE_EXPORT_FILENAME, mPendingExportFilename);
882    }
883
884    /**
885     * @return true if the project is edited
886     */
887    protected boolean isProjectEdited() {
888        return mProjectEditState;
889    }
890
891    /**
892     * Enter the disabled state
893     *
894     * @param statusStringId The status string id
895     */
896    protected abstract void enterDisabledState(int statusStringId);
897
898    /**
899     * Enter the transitional state
900     *
901     * @param statusStringId The status string id
902     */
903    protected abstract void enterTransitionalState(int statusStringId);
904
905    /**
906     * Enter the ready state
907     */
908    protected abstract void enterReadyState();
909
910    /**
911     * Show the preview frame at the current playhead position
912     *
913     * @return true if the surface is created, false otherwise.
914     */
915    protected abstract boolean showPreviewFrame();
916
917    /**
918     * The duration of the timeline has changed
919     */
920    protected abstract void updateTimelineDuration();
921
922    /**
923     * Move the playhead to the specified position
924     *
925     * @param timeMs The time position
926     */
927    protected abstract void movePlayhead(long timeMs);
928
929    /**
930     * Change the aspect ratio
931     *
932     * @param aspectRatio The new aspect ratio
933     */
934    protected abstract void setAspectRatio(int aspectRatio);
935
936    /**
937     * Initialize the project when restored from storage.
938     *
939     * @param updateUI true to update the UI
940     *
941     * Note that this method may be called also after the project was loaded
942     */
943    protected abstract void initializeFromProject(boolean updateUI);
944
945    /**
946     * @return The media layout
947     */
948    protected abstract MediaLinearLayout getMediaLayout();
949
950    /**
951     * @return The overlay layout
952     */
953    protected abstract OverlayLinearLayout getOverlayLayout();
954
955    /**
956     * @return The audio layout
957     */
958    protected abstract AudioTrackLinearLayout getAudioTrackLayout();
959
960    /**
961     * The export is progressing
962     */
963    protected abstract void onExportProgress(int progress);
964
965    /**
966     * The export has completed
967     */
968    protected abstract void onExportComplete();
969
970    /**
971     * @param projectEdited true if the project is edited
972     */
973    protected abstract void onProjectEditStateChange(boolean projectEdited);
974}
975