Home | History | Annotate | Download | only in videoeditor
      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 package com.android.videoeditor;
     18 
     19 import java.util.List;
     20 
     21 import android.app.Activity;
     22 import android.content.Context;
     23 import android.content.Intent;
     24 import android.graphics.Bitmap;
     25 import android.media.AudioManager;
     26 import android.media.videoeditor.AudioTrack;
     27 import android.media.videoeditor.MediaItem;
     28 import android.media.videoeditor.MediaVideoItem;
     29 import android.media.videoeditor.Transition;
     30 import android.net.Uri;
     31 import android.os.Bundle;
     32 import android.util.Log;
     33 import android.widget.Toast;
     34 
     35 import com.android.videoeditor.service.ApiService;
     36 import com.android.videoeditor.service.ApiServiceListener;
     37 import com.android.videoeditor.service.MovieAudioTrack;
     38 import com.android.videoeditor.service.MovieEffect;
     39 import com.android.videoeditor.service.MovieMediaItem;
     40 import com.android.videoeditor.service.MovieOverlay;
     41 import com.android.videoeditor.service.MovieTransition;
     42 import com.android.videoeditor.service.VideoEditorProject;
     43 import com.android.videoeditor.widgets.AudioTrackLinearLayout;
     44 import com.android.videoeditor.widgets.MediaLinearLayout;
     45 import com.android.videoeditor.widgets.OverlayLinearLayout;
     46 
     47 /**
     48  * This activity handles callbacks from the service and manages the project
     49  */
     50 public 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