Home | History | Annotate | Download | only in service
      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.service;
     18 
     19 import java.io.File;
     20 import java.io.FileNotFoundException;
     21 import java.io.FileOutputStream;
     22 import java.io.IOException;
     23 import java.io.InputStream;
     24 import java.util.ArrayList;
     25 import java.util.Collections;
     26 import java.util.Comparator;
     27 import java.util.HashMap;
     28 import java.util.Iterator;
     29 import java.util.List;
     30 import java.util.Map;
     31 import java.util.concurrent.BlockingQueue;
     32 import java.util.concurrent.LinkedBlockingQueue;
     33 
     34 import android.app.Service;
     35 import android.content.ContentValues;
     36 import android.content.Context;
     37 import android.content.Intent;
     38 import android.database.Cursor;
     39 import android.graphics.Bitmap;
     40 import android.graphics.Rect;
     41 import android.media.videoeditor.AudioTrack;
     42 import android.media.videoeditor.Effect;
     43 import android.media.videoeditor.EffectColor;
     44 import android.media.videoeditor.EffectKenBurns;
     45 import android.media.videoeditor.ExtractAudioWaveformProgressListener;
     46 import android.media.videoeditor.MediaImageItem;
     47 import android.media.videoeditor.MediaItem;
     48 import android.media.videoeditor.MediaProperties;
     49 import android.media.videoeditor.MediaVideoItem;
     50 import android.media.videoeditor.Overlay;
     51 import android.media.videoeditor.OverlayFrame;
     52 import android.media.videoeditor.Transition;
     53 import android.media.videoeditor.TransitionAlpha;
     54 import android.media.videoeditor.TransitionCrossfade;
     55 import android.media.videoeditor.TransitionFadeBlack;
     56 import android.media.videoeditor.TransitionSliding;
     57 import android.media.videoeditor.VideoEditor;
     58 import android.media.videoeditor.VideoEditorFactory;
     59 import android.media.videoeditor.WaveformData;
     60 import android.media.videoeditor.MediaItem.GetThumbnailListCallback;
     61 import android.media.videoeditor.VideoEditor.ExportProgressListener;
     62 import android.media.videoeditor.VideoEditor.MediaProcessingProgressListener;
     63 import android.net.Uri;
     64 import android.os.Bundle;
     65 import android.os.Handler;
     66 import android.os.IBinder;
     67 import android.os.Looper;
     68 import android.provider.MediaStore;
     69 import android.provider.MediaStore.Audio;
     70 import android.provider.MediaStore.Images;
     71 import android.provider.MediaStore.Video;
     72 import android.util.Log;
     73 
     74 import com.android.videoeditor.R;
     75 import com.android.videoeditor.util.FileUtils;
     76 import com.android.videoeditor.util.ImageUtils;
     77 import com.android.videoeditor.util.MediaItemUtils;
     78 import com.android.videoeditor.util.StringUtils;
     79 
     80 /**
     81  * VideoEditor service API
     82  */
     83 public class ApiService extends Service {
     84     // Logging
     85     private static final String TAG = "VEApiService";
     86 
     87     // Additional updates
     88     public static final int ACTION_UPDATE_FRAME = MediaProcessingProgressListener.ACTION_DECODE + 100;
     89     public static final int ACTION_NO_FRAME_UPDATE = MediaProcessingProgressListener.ACTION_DECODE + 101;
     90 
     91     // Parameters
     92     private static final String PARAM_OP = "op";
     93     private static final String PARAM_REQUEST_ID = "rid";
     94     private static final String PARAM_PROJECT_PATH = "project";
     95     private static final String PARAM_FILENAME = "filename";
     96     private static final String PARAM_STORYBOARD_ITEM_ID = "item_id";
     97     private static final String PARAM_RELATIVE_STORYBOARD_ITEM_ID = "r_item_id";
     98     private static final String PARAM_PROGRESS_VALUE = "prog_value";
     99     private static final String PARAM_EXCEPTION = "ex";
    100     private static final String PARAM_START_TIME = "s_time";
    101     private static final String PARAM_END_TIME = "e_time";
    102     private static final String PARAM_DURATION = "duration";
    103     private static final String PARAM_WIDTH = "width";
    104     private static final String PARAM_HEIGHT = "height";
    105     private static final String PARAM_BITRATE = "bitrate";
    106     private static final String PARAM_MEDIA_ITEM_RENDERING_MODE = "rm";
    107     private static final String PARAM_MEDIA_ITEM_START_RECT = "start_rect";
    108     private static final String PARAM_MEDIA_ITEM_END_RECT = "end_rect";
    109     private static final String PARAM_EFFECT_TYPE = "e_type";
    110     private static final String PARAM_EFFECT_PARAM = "e_param";
    111     private static final String PARAM_TRANSITION_BEHAVIOR = "behavior";
    112     private static final String PARAM_TRANSITION_MASK = "t_mask";
    113     private static final String PARAM_TRANSITION_BLENDING = "t_blending";
    114     private static final String PARAM_TRANSITION_INVERT = "t_invert";
    115     private static final String PARAM_TRANSITION_DIRECTION = "t_dir";
    116     private static final String PARAM_INTENT = "req_intent";
    117     private static final String PARAM_PROJECT_NAME = "name";
    118     private static final String PARAM_MOVIES_FILENAMES = "movies";
    119     private static final String PARAM_PHOTOS_FILENAMES = "images";
    120     private static final String PARAM_ASPECT_RATIO = "aspect_ratio";
    121     private static final String PARAM_BEGIN_BOUNDARY = "b_boundary";
    122     private static final String PARAM_END_BOUNDARY = "e_boundary";
    123     private static final String PARAM_ATTRIBUTES = "attributes";
    124     private static final String PARAM_VOLUME = "volume";
    125     private static final String PARAM_LOOP = "loop";
    126     private static final String PARAM_MUTE = "mute";
    127     private static final String PARAM_DUCK = "duck";
    128     private static final String PARAM_MOVIE_URI = "uri";
    129     private static final String PARAM_THEME = "theme";
    130     private static final String PARAM_ACTION = "action";
    131     private static final String PARAM_COUNT = "count";
    132     private static final String PARAM_TOKEN = "token";
    133     private static final String PARAM_INDICES = "indices";
    134     private static final String PARAM_CANCELLED = "cancelled";
    135 
    136     // Operations
    137     private static final int OP_VIDEO_EDITOR_CREATE = 1;
    138     private static final int OP_VIDEO_EDITOR_LOAD = 2;
    139     private static final int OP_VIDEO_EDITOR_SAVE = 3;
    140     private static final int OP_VIDEO_EDITOR_EXPORT = 4;
    141     private static final int OP_VIDEO_EDITOR_CANCEL_EXPORT = 5;
    142     private static final int OP_VIDEO_EDITOR_EXPORT_STATUS = 6;
    143     private static final int OP_VIDEO_EDITOR_RELEASE = 8;
    144     private static final int OP_VIDEO_EDITOR_DELETE = 9;
    145     private static final int OP_VIDEO_EDITOR_SET_ASPECT_RATIO = 10;
    146     private static final int OP_VIDEO_EDITOR_APPLY_THEME = 11;
    147     private static final int OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS = 12;
    148     private static final int OP_VIDEO_EDITOR_LOAD_PROJECTS = 13;
    149 
    150     private static final int OP_MEDIA_ITEM_ADD_VIDEO_URI = 100;
    151     private static final int OP_MEDIA_ITEM_ADD_IMAGE_URI = 101;
    152     private static final int OP_MEDIA_ITEM_MOVE = 102;
    153     private static final int OP_MEDIA_ITEM_REMOVE = 103;
    154     private static final int OP_MEDIA_ITEM_SET_RENDERING_MODE = 104;
    155     private static final int OP_MEDIA_ITEM_SET_DURATION = 105;
    156     private static final int OP_MEDIA_ITEM_SET_BOUNDARIES = 106;
    157     private static final int OP_MEDIA_ITEM_SET_VOLUME = 107;
    158     private static final int OP_MEDIA_ITEM_SET_MUTE = 108;
    159     private static final int OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM = 109;
    160     private static final int OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS = 110;
    161     private static final int OP_MEDIA_ITEM_GET_THUMBNAILS = 112;
    162     private static final int OP_MEDIA_ITEM_LOAD = 113;
    163     private static final int OP_MEDIA_ITEM_LOAD_STATUS = 114;
    164 
    165     private static final int OP_EFFECT_ADD_COLOR = 200;
    166     private static final int OP_EFFECT_ADD_IMAGE_KEN_BURNS = 201;
    167     private static final int OP_EFFECT_REMOVE = 202;
    168 
    169     private static final int OP_TRANSITION_INSERT_ALPHA = 300;
    170     private static final int OP_TRANSITION_INSERT_CROSSFADE = 301;
    171     private static final int OP_TRANSITION_INSERT_FADE_BLACK = 302;
    172     private static final int OP_TRANSITION_INSERT_SLIDING = 303;
    173     private static final int OP_TRANSITION_REMOVE = 304;
    174     private static final int OP_TRANSITION_SET_DURATION = 305;
    175     private static final int OP_TRANSITION_GET_THUMBNAIL = 306;
    176 
    177     private static final int OP_OVERLAY_ADD = 400;
    178     private static final int OP_OVERLAY_REMOVE = 401;
    179     private static final int OP_OVERLAY_SET_START_TIME = 402;
    180     private static final int OP_OVERLAY_SET_DURATION = 403;
    181     private static final int OP_OVERLAY_SET_ATTRIBUTES = 404;
    182 
    183     private static final int OP_AUDIO_TRACK_ADD = 500;
    184     private static final int OP_AUDIO_TRACK_REMOVE = 501;
    185     private static final int OP_AUDIO_TRACK_SET_VOLUME = 502;
    186     private static final int OP_AUDIO_TRACK_SET_MUTE = 503;
    187     private static final int OP_AUDIO_TRACK_SET_BOUNDARIES = 505;
    188     private static final int OP_AUDIO_TRACK_SET_LOOP = 506;
    189     private static final int OP_AUDIO_TRACK_SET_DUCK = 507;
    190     private static final int OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM = 508;
    191     private static final int OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS = 509;
    192 
    193     private static final int DUCK_THRESHOLD = 20;
    194     private static final int DUCK_TRACK_VOLUME = 65;
    195     // The default audio track volume
    196     private static final int DEFAULT_AUDIO_TRACK_VOLUME = 50;
    197 
    198     // Static member variables
    199     private static final Map<String, Intent> mPendingIntents = new HashMap<String, Intent>();
    200     private static final List<ApiServiceListener> mListeners = new ArrayList<ApiServiceListener>();
    201     private static final IntentPool mIntentPool = new IntentPool(8);
    202     private static VideoEditorProject mVideoProject;
    203     private static VideoEditor mVideoEditor;
    204     private static ServiceMediaProcessingProgressListener mGeneratePreviewListener;
    205     private static volatile boolean mExportCancelled;
    206 
    207     private IntentProcessor mVideoThread;
    208     private IntentProcessor mAudioThread;
    209     private IntentProcessor mThumbnailThread;
    210     private Handler mHandler;
    211 
    212     private final Runnable mStopRunnable = new Runnable() {
    213         @Override
    214         public void run() {
    215             if (mPendingIntents.size() == 0) {
    216                 logd("Stop runnable: Stopping service");
    217                 stopSelf();
    218             }
    219         }
    220     };
    221 
    222     /**
    223      * Generate preview listener
    224      */
    225     private final class ServiceMediaProcessingProgressListener
    226             implements VideoEditor.MediaProcessingProgressListener {
    227         // Instance variables
    228         private final String mProjectPath;
    229 
    230         /**
    231          * Constructor
    232          *
    233          * @param projectPath The project path
    234          */
    235         public ServiceMediaProcessingProgressListener(String projectPath) {
    236             mProjectPath = projectPath;
    237         }
    238 
    239         @Override
    240         public void onProgress(Object item, int action, int progress) {
    241             final Intent intent = mIntentPool.get();
    242             intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS);
    243             intent.putExtra(PARAM_PROJECT_PATH, mProjectPath);
    244             intent.putExtra(PARAM_ACTION, action);
    245             intent.putExtra(PARAM_PROGRESS_VALUE, progress);
    246 
    247             if (item == null) { // Last callback uses null
    248             } else if (item instanceof MediaItem) {
    249                 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((MediaItem)item).getId());
    250                 intent.putExtra(PARAM_ATTRIBUTES, MediaItem.class.getCanonicalName());
    251             } else if (item instanceof Transition) {
    252                 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((Transition)item).getId());
    253                 intent.putExtra(PARAM_ATTRIBUTES, Transition.class.getCanonicalName());
    254             } else if (item instanceof AudioTrack) {
    255                 intent.putExtra(PARAM_STORYBOARD_ITEM_ID, ((AudioTrack)item).getId());
    256                 intent.putExtra(PARAM_ATTRIBUTES, AudioTrack.class.getCanonicalName());
    257             } else {
    258                 Log.w(TAG, "Unsupported storyboard item type: " + item.getClass());
    259                 return;
    260             }
    261 
    262             completeRequest(intent, null, null, null, null, true);
    263         }
    264     }
    265 
    266     /**
    267      * @return A unique id
    268      */
    269     public static String generateId() {
    270         return StringUtils.randomString(6);
    271     }
    272 
    273     /**
    274      * Register a listener
    275      *
    276      * @param listener The listener
    277      */
    278     public static void registerListener(ApiServiceListener listener) {
    279         mListeners.add(listener);
    280     }
    281 
    282     /**
    283      * Unregister a listener
    284      *
    285      * @param listener The listener
    286      */
    287     public static void unregisterListener(ApiServiceListener listener) {
    288         mListeners.remove(listener);
    289     }
    290 
    291     /**
    292      * Load the projects
    293      *
    294      * @param context The context
    295      */
    296     public static void loadProjects(Context context) {
    297         final Intent intent = mIntentPool.get(context, ApiService.class);
    298         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_LOAD_PROJECTS);
    299 
    300         startCommand(context, intent);
    301     }
    302 
    303     /**
    304      * Create a new VideoEditor project
    305      *
    306      * @param context The context
    307      * @param projectPath The project path
    308      * @param projectName The project name
    309      * @param movies The array of movie file names to add to the newly
    310      *      created project
    311      * @param photos The array of photo file names to add to the newly
    312      *      created project
    313      * @param themeType The theme type
    314      */
    315     public static void createVideoEditor(Context context, String projectPath, String projectName,
    316                 String[] movies, String[] photos, String themeType) {
    317         final Intent intent = mIntentPool.get(context, ApiService.class);
    318         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_CREATE);
    319         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    320         intent.putExtra(PARAM_PROJECT_NAME, projectName);
    321         intent.putExtra(PARAM_MOVIES_FILENAMES, movies);
    322         intent.putExtra(PARAM_PHOTOS_FILENAMES, photos);
    323         intent.putExtra(PARAM_THEME, themeType);
    324 
    325         startCommand(context, intent);
    326     }
    327 
    328     /**
    329      * Create a new VideoEditor project
    330      *
    331      * @param context The context
    332      * @param projectPath The project path
    333      */
    334     public static void loadVideoEditor(Context context, String projectPath) {
    335         final Intent intent = mIntentPool.get(context, ApiService.class);
    336         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_LOAD);
    337         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    338 
    339         startCommand(context, intent);
    340     }
    341 
    342     /**
    343      * Export the VideoEditor movie
    344      *
    345      * @param context The context
    346      * @param projectPath The project path
    347      * @param filename The export filename
    348      * @param height The output movie height
    349      * @param bitrate The output movie bitrate
    350      */
    351     public static void exportVideoEditor(Context context, String projectPath, String filename,
    352             int height, int bitrate) {
    353         final Intent intent = mIntentPool.get(context, ApiService.class);
    354         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT);
    355         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    356         intent.putExtra(PARAM_FILENAME, filename);
    357         intent.putExtra(PARAM_HEIGHT, height);
    358         intent.putExtra(PARAM_BITRATE, bitrate);
    359 
    360         startCommand(context, intent);
    361     }
    362 
    363     /**
    364      * Check if export is pending
    365      *
    366      * @param projectPath The project path
    367      * @param filename The export filename
    368      *
    369      * @return true if the export is pending
    370      */
    371     public static boolean isVideoEditorExportPending(String projectPath, String filename) {
    372         for (Intent intent : mPendingIntents.values()) {
    373             final int op = intent.getIntExtra(PARAM_OP, -1);
    374             if (op == OP_VIDEO_EDITOR_EXPORT) {
    375                 String pp = intent.getStringExtra(PARAM_PROJECT_PATH);
    376                 if (pp.equals(projectPath)) {
    377                     String fn = intent.getStringExtra(PARAM_FILENAME);
    378                     if (fn.equals(filename)) {
    379                         return true;
    380                     }
    381                 }
    382             }
    383         }
    384 
    385         return false;
    386     }
    387 
    388     /**
    389      * Cancel the export of the specified VideoEditor movie
    390      *
    391      * @param context The context
    392      * @param projectPath The project path
    393      * @param filename The export filename
    394      */
    395     public static void cancelExportVideoEditor(Context context, String projectPath,
    396             String filename) {
    397         mExportCancelled = true;
    398         final Intent intent = mIntentPool.get(context, ApiService.class);
    399         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_CANCEL_EXPORT);
    400         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    401         intent.putExtra(PARAM_FILENAME, filename);
    402 
    403         startCommand(context, intent);
    404     }
    405 
    406     /**
    407      * Change the aspect ratio
    408      *
    409      * @param context The context
    410      * @param projectPath The project path
    411      * @param aspectRatio The aspect ratio
    412      */
    413     public static void setAspectRatio(Context context, String projectPath, int aspectRatio) {
    414         final Intent intent = mIntentPool.get(context, ApiService.class);
    415         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_SET_ASPECT_RATIO);
    416         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    417         intent.putExtra(PARAM_ASPECT_RATIO, aspectRatio);
    418 
    419         startCommand(context, intent);
    420     }
    421 
    422     /**
    423      * Apply a theme
    424      *
    425      * @param context The context
    426      * @param projectPath The project path
    427      * @param theme The theme
    428      */
    429      public static void applyTheme(Context context, String projectPath, String theme) {
    430          final Intent intent = mIntentPool.get(context, ApiService.class);
    431          intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_APPLY_THEME);
    432          intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    433          intent.putExtra(PARAM_THEME, theme);
    434 
    435          startCommand(context, intent);
    436      }
    437 
    438     /**
    439      * Checks if the service is busy modifying the timeline. While
    440      * the video editor is busy the application should not attempt
    441      * to preview the movie.
    442      *
    443      * @param projectPath The project path
    444      *
    445      * @return {@code true} if the video editor is modifying the timeline
    446      */
    447     public static boolean isProjectBeingEdited(String projectPath) {
    448         for (Intent intent : mPendingIntents.values()) {
    449             final int op = intent.getIntExtra(PARAM_OP, -1);
    450             switch (op) {
    451                 // When these operations are pending the video editor is not busy.
    452                 case OP_VIDEO_EDITOR_LOAD_PROJECTS:
    453                 case OP_VIDEO_EDITOR_SAVE:
    454                 case OP_MEDIA_ITEM_SET_VOLUME:
    455                 case OP_MEDIA_ITEM_SET_MUTE:
    456                 case OP_MEDIA_ITEM_GET_THUMBNAILS:
    457                 case OP_MEDIA_ITEM_LOAD:
    458                 case OP_TRANSITION_GET_THUMBNAIL:
    459                 case OP_AUDIO_TRACK_SET_VOLUME:
    460                 case OP_AUDIO_TRACK_SET_MUTE: {
    461                     break;
    462                 }
    463 
    464                 default: {
    465                     final String pp = intent.getStringExtra(PARAM_PROJECT_PATH);
    466                     if (pp != null && pp.equals(projectPath)) {
    467                         return true;
    468                     }
    469                     break;
    470                 }
    471             }
    472         }
    473 
    474         return false;
    475     }
    476 
    477     /**
    478      * Save the VideoEditor project
    479      *
    480      * @param context The context
    481      * @param projectPath The project path
    482      */
    483     public static void saveVideoEditor(Context context, String projectPath) {
    484         final Intent intent = mIntentPool.get(context, ApiService.class);
    485         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_SAVE);
    486         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    487 
    488         startCommand(context, intent);
    489     }
    490 
    491     /**
    492      * Release the VideoEditor project
    493      *
    494      * @param context The context
    495      * @param projectPath The project path
    496      */
    497     public static void releaseVideoEditor(Context context, String projectPath) {
    498         final Intent intent = mIntentPool.get(context, ApiService.class);
    499         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_RELEASE);
    500         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    501 
    502         startCommand(context, intent);
    503     }
    504 
    505     /**
    506      * Delete the project specified by the project path
    507      *
    508      * @param context The context
    509      * @param projectPath The project path
    510      */
    511     public static void deleteProject(Context context, String projectPath) {
    512         final Intent intent = mIntentPool.get(context, ApiService.class);
    513         intent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_DELETE);
    514         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    515 
    516         startCommand(context, intent);
    517     }
    518 
    519     /**
    520      * Add a new video media item after the specified media item id
    521      *
    522      * @param context The context
    523      * @param projectPath The project path
    524      * @param mediaItemId The mediaItem id
    525      * @param afterMediaItemId The id of the media item preceding the media item
    526      * @param uri The media item URI
    527      * @param renderingMode The rendering mode
    528      * @param themeId The theme id
    529      */
    530     public static void addMediaItemVideoUri(Context context, String projectPath,
    531             String mediaItemId, String afterMediaItemId, Uri uri, int renderingMode,
    532             String themeId) {
    533         final Intent intent = mIntentPool.get(context, ApiService.class);
    534         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_ADD_VIDEO_URI);
    535         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    536         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    537         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId);
    538         intent.putExtra(PARAM_FILENAME, uri);
    539         intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode);
    540         intent.putExtra(PARAM_THEME, themeId);
    541 
    542         startCommand(context, intent);
    543     }
    544 
    545     /**
    546      * Add a new image media item after the specified media item id
    547      *
    548      * @param context The context
    549      * @param projectPath The project path
    550      * @param mediaItemId The mediaItem id
    551      * @param afterMediaItemId The id of the media item preceding the media item
    552      * @param uri The media item URI
    553      * @param renderingMode The rendering mode
    554      * @param durationMs The duration of the item (for images only, ignored for videos)
    555      * @param themeId The theme id
    556      */
    557     public static void addMediaItemImageUri(Context context, String projectPath,
    558             String mediaItemId, String afterMediaItemId, Uri uri, int renderingMode,
    559             long durationMs, String themeId) {
    560         final Intent intent = mIntentPool.get(context, ApiService.class);
    561         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_ADD_IMAGE_URI);
    562         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    563         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    564         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId);
    565         intent.putExtra(PARAM_FILENAME, uri);
    566         intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode);
    567         intent.putExtra(PARAM_DURATION, durationMs);
    568         intent.putExtra(PARAM_THEME, themeId);
    569 
    570         startCommand(context, intent);
    571     }
    572 
    573     /**
    574      * Download or make a copy of an image from the specified URI
    575      *
    576      * @param context The context
    577      * @param projectPath The project path
    578      * @param uri The media item URI
    579      * @param mimeType The MIME type
    580      */
    581     public static void loadMediaItem(Context context, String projectPath, Uri uri,
    582             String mimeType) {
    583         final Intent intent = mIntentPool.get(context, ApiService.class);
    584         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_LOAD);
    585         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    586         intent.putExtra(PARAM_FILENAME, uri);
    587         intent.putExtra(PARAM_ATTRIBUTES, mimeType);
    588 
    589         startCommand(context, intent);
    590     }
    591 
    592     /**
    593      * Move a media item after the specified media id
    594      *
    595      * @param context The context
    596      * @param projectPath The project path
    597      * @param mediaItemId The id of the media item to move
    598      * @param afterMediaItemId The id of the relative media item
    599      * @param themeId The theme id
    600      */
    601     public static void moveMediaItem(Context context, String projectPath,
    602             String mediaItemId, String afterMediaItemId, String themeId) {
    603         final Intent intent = mIntentPool.get(context, ApiService.class);
    604         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_MOVE);
    605         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    606         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    607         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId);
    608         intent.putExtra(PARAM_THEME, themeId);
    609 
    610         startCommand(context, intent);
    611     }
    612 
    613     /**
    614      * Remove a media item
    615      *
    616      * @param context The context
    617      * @param projectPath The project path
    618      * @param mediaItemId The id of the media item to remove
    619      * @param themeId The theme id
    620      */
    621     public static void removeMediaItem(Context context, String projectPath, String mediaItemId,
    622             String themeId) {
    623         final Intent intent = mIntentPool.get(context, ApiService.class);
    624         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_REMOVE);
    625         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    626         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    627         intent.putExtra(PARAM_THEME, themeId);
    628 
    629         startCommand(context, intent);
    630     }
    631 
    632     /**
    633      * Set the rendering mode for a media item
    634      *
    635      * @param context The context
    636      * @param projectPath The project path
    637      * @param mediaItemId The id of the media item
    638      */
    639     public static void setMediaItemRenderingMode(Context context, String projectPath,
    640             String mediaItemId, int renderingMode) {
    641         final Intent intent = mIntentPool.get(context, ApiService.class);
    642         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_RENDERING_MODE);
    643         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    644         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    645         intent.putExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, renderingMode);
    646 
    647         startCommand(context, intent);
    648     }
    649 
    650     /**
    651      * Get the thumbnails of the specified size
    652      *
    653      * @param context The context
    654      * @param projectPath The project path
    655      * @param mediaItemId The id of the media item
    656      * @param width The width
    657      * @param height The height
    658      * @param startMs The start time in milliseconds
    659      * @param endMs The end time in milliseconds
    660      * @param count The number of thumbnails
    661      */
    662     public static void getMediaItemThumbnails(Context context,
    663             String projectPath, String mediaItemId, int width, int height,
    664             long startMs, long endMs, int count, int token, int[] indices) {
    665         final Intent intent = mIntentPool.get(context, ApiService.class);
    666         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_GET_THUMBNAILS);
    667         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    668         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    669         intent.putExtra(PARAM_WIDTH, width);
    670         intent.putExtra(PARAM_HEIGHT, height);
    671         intent.putExtra(PARAM_START_TIME, startMs);
    672         intent.putExtra(PARAM_END_TIME, endMs);
    673         intent.putExtra(PARAM_COUNT, count);
    674         intent.putExtra(PARAM_TOKEN, token);
    675         intent.putExtra(PARAM_INDICES, indices);
    676 
    677         startCommand(context, intent);
    678     }
    679 
    680     /**
    681      * Set the media item duration
    682      *
    683      * @param context The context
    684      * @param projectPath The project path
    685      * @param mediaItemId The id of the media item
    686      * @param durationMs The media item duration
    687      */
    688     public static void setMediaItemDuration(Context context, String projectPath,
    689             String mediaItemId, long durationMs) {
    690         final Intent intent = mIntentPool.get(context, ApiService.class);
    691         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_DURATION);
    692         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    693         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    694         intent.putExtra(PARAM_DURATION, durationMs);
    695 
    696         startCommand(context, intent);
    697     }
    698 
    699     /**
    700      * Set the media item boundaries
    701      *
    702      * @param context The context
    703      * @param projectPath The project path
    704      * @param mediaItemId The id of the media item
    705      * @param beginBoundaryMs The media item begin boundary
    706      * @param endBoundaryMs The media item end boundary
    707      */
    708     public static void setMediaItemBoundaries(Context context, String projectPath,
    709             String mediaItemId, long beginBoundaryMs, long endBoundaryMs) {
    710         final Intent intent = mIntentPool.get(context, ApiService.class);
    711         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_BOUNDARIES);
    712         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    713         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    714         intent.putExtra(PARAM_BEGIN_BOUNDARY, beginBoundaryMs);
    715         intent.putExtra(PARAM_END_BOUNDARY, endBoundaryMs);
    716 
    717         startCommand(context, intent);
    718     }
    719 
    720     /**
    721      * Set the media item volume (MediaVideoItem only)
    722      *
    723      * @param context The context
    724      * @param projectPath The project path
    725      * @param mediaItemId The id of the media item
    726      * @param volumePercentage The volume
    727      */
    728     public static void setMediaItemVolume(Context context, String projectPath,
    729             String mediaItemId, int volumePercentage) {
    730         final Intent intent = mIntentPool.get(context, ApiService.class);
    731         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_VOLUME);
    732         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    733         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    734         intent.putExtra(PARAM_VOLUME, volumePercentage);
    735 
    736         startCommand(context, intent);
    737     }
    738 
    739     /**
    740      * Mute/unmute the media item (MediaVideoItem only)
    741      *
    742      * @param context The context
    743      * @param projectPath The project path
    744      * @param mediaItemId The id of the media item
    745      * @param muted true to mute the media item
    746      */
    747     public static void setMediaItemMute(Context context, String projectPath, String mediaItemId,
    748             boolean muted) {
    749         final Intent intent = mIntentPool.get(context, ApiService.class);
    750         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_SET_MUTE);
    751         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    752         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    753         intent.putExtra(PARAM_MUTE, muted);
    754 
    755         startCommand(context, intent);
    756     }
    757 
    758     /**
    759      * Extract the media item audio waveform
    760      *
    761      * @param context The context
    762      * @param projectPath The project path
    763      * @param mediaItemId The id of the media item
    764      */
    765     public static void extractMediaItemAudioWaveform(Context context, String projectPath,
    766             String mediaItemId) {
    767         if (isMediaItemAudioWaveformPending(projectPath, mediaItemId)) {
    768             return;
    769         }
    770 
    771         final Intent intent = mIntentPool.get(context, ApiService.class);
    772         intent.putExtra(PARAM_OP, OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM);
    773         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    774         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItemId);
    775 
    776         startCommand(context, intent);
    777     }
    778 
    779     /**
    780      * Check if extract audio waveform is pending for the specified MediaItem
    781      *
    782      * @param projectPath The project path
    783      * @param mediaItemId The MediaItem id
    784      *
    785      * @return true if the extract audio waveform is pending
    786      */
    787     public static boolean isMediaItemAudioWaveformPending(String projectPath, String mediaItemId) {
    788         for (Intent intent : mPendingIntents.values()) {
    789             int op = intent.getIntExtra(PARAM_OP, -1);
    790             if (op == OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM) {
    791                 String pp = intent.getStringExtra(PARAM_PROJECT_PATH);
    792                 if (pp.equals(projectPath)) {
    793                     String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
    794                     if (mid.equals(mediaItemId)) {
    795                         return true;
    796                     }
    797                 }
    798             }
    799         }
    800 
    801         return false;
    802     }
    803 
    804     /**
    805      * Insert an alpha transition after the specified media item
    806      *
    807      * @param context The context
    808      * @param projectPath The project path
    809      * @param afterMediaItemId Insert the transition after the media item with this id
    810      * @param transitionId The transition id
    811      * @param durationMs The duration in milliseconds
    812      * @param behavior The transition behavior
    813      * @param maskRawResourceId The mask raw resource id
    814      * @param blending The transition blending
    815      * @param invert The transition invert
    816      */
    817     public static void insertAlphaTransition(Context context, String projectPath,
    818             String afterMediaItemId, String transitionId, long durationMs, int behavior,
    819             int maskRawResourceId, int blending, boolean invert) {
    820         final Intent intent = mIntentPool.get(context, ApiService.class);
    821         intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_ALPHA);
    822         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    823         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId);
    824         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId);
    825         intent.putExtra(PARAM_DURATION, durationMs);
    826         intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior);
    827         intent.putExtra(PARAM_TRANSITION_MASK, maskRawResourceId);
    828         intent.putExtra(PARAM_TRANSITION_BLENDING, blending);
    829         intent.putExtra(PARAM_TRANSITION_INVERT, invert);
    830 
    831         startCommand(context, intent);
    832     }
    833 
    834     /**
    835      * Insert an crossfade transition after the specified media item
    836      *
    837      * @param context The context
    838      * @param projectPath The project path
    839      * @param afterMediaItemId Insert the transition after the media item with this id
    840      * @param transitionId The transition id
    841      * @param durationMs The duration in milliseconds
    842      * @param behavior The transition behavior
    843      */
    844     public static void insertCrossfadeTransition(Context context, String projectPath,
    845             String afterMediaItemId, String transitionId, long durationMs, int behavior) {
    846         final Intent intent = mIntentPool.get(context, ApiService.class);
    847         intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_CROSSFADE);
    848         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    849         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId);
    850         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId);
    851         intent.putExtra(PARAM_DURATION, durationMs);
    852         intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior);
    853 
    854         startCommand(context, intent);
    855     }
    856 
    857     /**
    858      * Insert a fade-to-black transition after the specified media item
    859      *
    860      * @param context The context
    861      * @param projectPath The project path
    862      * @param afterMediaItemId Insert the transition after the media item with this id
    863      * @param transitionId The transition id
    864      * @param durationMs The duration in milliseconds
    865      * @param behavior The transition behavior
    866      */
    867     public static void insertFadeBlackTransition(Context context, String projectPath,
    868             String afterMediaItemId, String transitionId, long durationMs, int behavior) {
    869         final Intent intent = mIntentPool.get(context, ApiService.class);
    870         intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_FADE_BLACK);
    871         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    872         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId);
    873         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId);
    874         intent.putExtra(PARAM_DURATION, durationMs);
    875         intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior);
    876 
    877         startCommand(context, intent);
    878     }
    879 
    880     /**
    881      * Insert a sliding transition after the specified media item
    882      *
    883      * @param context The context
    884      * @param projectPath The project path
    885      * @param afterMediaItemId Insert the transition after the media item with this id
    886      * @param transitionId The transition id
    887      * @param durationMs The duration in milliseconds
    888      * @param behavior The transition behavior
    889      * @param direction The slide direction
    890      */
    891     public static void insertSlidingTransition(Context context, String projectPath,
    892             String afterMediaItemId, String transitionId, long durationMs, int behavior,
    893             int direction) {
    894         final Intent intent = mIntentPool.get(context, ApiService.class);
    895         intent.putExtra(PARAM_OP, OP_TRANSITION_INSERT_SLIDING);
    896         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    897         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, afterMediaItemId);
    898         intent.putExtra(PARAM_DURATION, durationMs);
    899         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId);
    900         intent.putExtra(PARAM_TRANSITION_BEHAVIOR, behavior);
    901         intent.putExtra(PARAM_TRANSITION_DIRECTION, direction);
    902 
    903         startCommand(context, intent);
    904     }
    905 
    906     /**
    907      * Remove a transition
    908      *
    909      * @param context The context
    910      * @param projectPath The project path
    911      * @param transitionId The id of the transition to remove
    912      */
    913     public static void removeTransition(Context context, String projectPath, String transitionId) {
    914         final Intent intent = mIntentPool.get(context, ApiService.class);
    915         intent.putExtra(PARAM_OP, OP_TRANSITION_REMOVE);
    916         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    917         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId);
    918 
    919         startCommand(context, intent);
    920     }
    921 
    922     /**
    923      * Set a transition duration
    924      *
    925      * @param context The context
    926      * @param projectPath The project path
    927      * @param transitionId The id of the transition
    928      * @param durationMs The transition duration
    929      */
    930     public static void setTransitionDuration(Context context, String projectPath,
    931             String transitionId, long durationMs) {
    932         final Intent intent = mIntentPool.get(context, ApiService.class);
    933         intent.putExtra(PARAM_OP, OP_TRANSITION_SET_DURATION);
    934         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    935         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId);
    936         intent.putExtra(PARAM_DURATION, durationMs);
    937 
    938         startCommand(context, intent);
    939     }
    940 
    941     /**
    942      * Get the thumbnail of the specified height
    943      *
    944      * @param context The context
    945      * @param projectPath The project path
    946      * @param transitionId The id of the transition
    947      * @param height The height
    948      */
    949     public static void getTransitionThumbnails(Context context, String projectPath,
    950             String transitionId, int height) {
    951         final Intent intent = mIntentPool.get(context, ApiService.class);
    952         intent.putExtra(PARAM_OP, OP_TRANSITION_GET_THUMBNAIL);
    953         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
    954         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, transitionId);
    955         intent.putExtra(PARAM_HEIGHT, height);
    956 
    957         startCommand(context, intent);
    958     }
    959 
    960     /**
    961      * Check if the transition thumbnailing is in progress
    962      *
    963      * @param projectPath The project path
    964      * @param transitionId The id of the transition
    965      *
    966      * @return true if the transition thumbnailing is in progress
    967      */
    968     public static boolean isTransitionThumbnailsPending(String projectPath, String transitionId) {
    969         for (Intent intent : mPendingIntents.values()) {
    970             int op = intent.getIntExtra(PARAM_OP, -1);
    971             if (op == OP_TRANSITION_GET_THUMBNAIL) {
    972                 String pp = intent.getStringExtra(PARAM_PROJECT_PATH);
    973                 if (pp.equals(projectPath)) {
    974                     String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
    975                     if (mid.equals(transitionId)) {
    976                         return true;
    977                     }
    978                 }
    979             }
    980         }
    981 
    982         return false;
    983     }
    984 
    985     /**
    986      * Add a color effect
    987      *
    988      * @param context The context
    989      * @param projectPath The project path
    990      * @param mediaItemId The media item id
    991      * @param effectId The effect id
    992      * @param startTimeMs The start time in milliseconds
    993      * @param durationMs The duration in milliseconds
    994      * @param type The effect type
    995      * @param param The effect param (if any)
    996      */
    997     public static void addEffectColor(Context context, String projectPath, String mediaItemId,
    998             String effectId, long startTimeMs, long durationMs, int type, int param) {
    999         final Intent intent = mIntentPool.get(context, ApiService.class);
   1000         intent.putExtra(PARAM_OP, OP_EFFECT_ADD_COLOR);
   1001         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1002         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1003         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId);
   1004         intent.putExtra(PARAM_START_TIME, startTimeMs);
   1005         intent.putExtra(PARAM_DURATION, durationMs);
   1006         intent.putExtra(PARAM_EFFECT_TYPE, type);
   1007         intent.putExtra(PARAM_EFFECT_PARAM, param);
   1008 
   1009         startCommand(context, intent);
   1010     }
   1011 
   1012     /**
   1013      * Add a Ken Burns effect
   1014      *
   1015      * @param context The context
   1016      * @param projectPath The project path
   1017      * @param mediaItemId The media item id
   1018      * @param effectId The effect id
   1019      * @param startTimeMs The start time
   1020      * @param durationMs The duration of the item
   1021      * @param startRect The start rectangle for the Ken Burns effect
   1022      * @param endRect The end rectangle for the Ken Burns effect
   1023      */
   1024     public static void addEffectKenBurns(Context context, String projectPath,
   1025             String mediaItemId, String effectId, long startTimeMs, long durationMs,
   1026             Rect startRect, Rect endRect) {
   1027         final Intent intent = mIntentPool.get(context, ApiService.class);
   1028         intent.putExtra(PARAM_OP, OP_EFFECT_ADD_IMAGE_KEN_BURNS);
   1029         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1030         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1031         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId);
   1032         intent.putExtra(PARAM_START_TIME, startTimeMs);
   1033         intent.putExtra(PARAM_DURATION, durationMs);
   1034         intent.putExtra(PARAM_MEDIA_ITEM_START_RECT, startRect);
   1035         intent.putExtra(PARAM_MEDIA_ITEM_END_RECT, endRect);
   1036 
   1037         startCommand(context, intent);
   1038     }
   1039 
   1040     /**
   1041      * Remove an effect
   1042      *
   1043      * @param context The context
   1044      * @param projectPath The project path
   1045      * @param mediaItemId The media item id
   1046      * @param effectId The id of the effect to remove
   1047      */
   1048     public static void removeEffect(Context context, String projectPath, String mediaItemId,
   1049             String effectId) {
   1050         final Intent intent = mIntentPool.get(context, ApiService.class);
   1051         intent.putExtra(PARAM_OP, OP_EFFECT_REMOVE);
   1052         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1053         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1054         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, effectId);
   1055 
   1056         startCommand(context, intent);
   1057     }
   1058 
   1059     /**
   1060      * Add an overlay
   1061      *
   1062      * @param context The context
   1063      * @param projectPath The project path
   1064      * @param mediaItemId The media item id
   1065      * @param userAttributes The overlay user attributes
   1066      * @param startTimeMs The start time in milliseconds
   1067      * @param durationMs The duration in milliseconds
   1068      */
   1069     public static void addOverlay(Context context, String projectPath, String mediaItemId,
   1070             String overlayId, Bundle userAttributes, long startTimeMs, long durationMs) {
   1071         final Intent intent = mIntentPool.get(context, ApiService.class);
   1072         intent.putExtra(PARAM_OP, OP_OVERLAY_ADD);
   1073         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1074         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1075         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId);
   1076         intent.putExtra(PARAM_START_TIME, startTimeMs);
   1077         intent.putExtra(PARAM_DURATION, durationMs);
   1078         intent.putExtra(PARAM_ATTRIBUTES, userAttributes);
   1079 
   1080         startCommand(context, intent);
   1081     }
   1082 
   1083     /**
   1084      * Remove an overlay
   1085      *
   1086      * @param context The context
   1087      * @param projectPath The project path
   1088      * @param mediaItemId The media item id
   1089      * @param overlayId The id of the overlay to remove
   1090      */
   1091     public static void removeOverlay(Context context, String projectPath, String mediaItemId,
   1092             String overlayId) {
   1093         final Intent intent = mIntentPool.get(context, ApiService.class);
   1094         intent.putExtra(PARAM_OP, OP_OVERLAY_REMOVE);
   1095         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1096         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1097         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId);
   1098 
   1099         startCommand(context, intent);
   1100     }
   1101 
   1102     /**
   1103      * Set the start time of an overlay
   1104      *
   1105      * @param context The context
   1106      * @param projectPath The project path
   1107      * @param mediaItemId The media item id
   1108      * @param overlayId The id of the overlay
   1109      * @param startTimeMs The start time in milliseconds
   1110      */
   1111     public static void setOverlayStartTime(Context context, String projectPath, String mediaItemId,
   1112             String overlayId, long startTimeMs) {
   1113         final Intent intent = mIntentPool.get(context, ApiService.class);
   1114         intent.putExtra(PARAM_OP, OP_OVERLAY_SET_START_TIME);
   1115         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1116         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1117         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId);
   1118         intent.putExtra(PARAM_START_TIME, startTimeMs);
   1119 
   1120         startCommand(context, intent);
   1121     }
   1122 
   1123     /**
   1124      * Set the duration of an overlay
   1125      *
   1126      * @param context The context
   1127      * @param projectPath The project path
   1128      * @param mediaItemId The media item id
   1129      * @param overlayId The id of the overlay
   1130      * @param durationMs The duration in milliseconds
   1131      */
   1132     public static void setOverlayDuration(Context context, String projectPath, String mediaItemId,
   1133             String overlayId, long durationMs) {
   1134         final Intent intent = mIntentPool.get(context, ApiService.class);
   1135         intent.putExtra(PARAM_OP, OP_OVERLAY_SET_DURATION);
   1136         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1137         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1138         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId);
   1139         intent.putExtra(PARAM_DURATION, durationMs);
   1140 
   1141         startCommand(context, intent);
   1142     }
   1143 
   1144     /**
   1145      * Set the user attributes of an overlay
   1146      *
   1147      * @param context The context
   1148      * @param projectPath The project path
   1149      * @param mediaItemId The media item id
   1150      * @param overlayId The id of the overlay
   1151      * @param userAttributes The user attributes
   1152      */
   1153     public static void setOverlayUserAttributes(Context context, String projectPath,
   1154             String mediaItemId, String overlayId, Bundle userAttributes) {
   1155         final Intent intent = mIntentPool.get(context, ApiService.class);
   1156         intent.putExtra(PARAM_OP, OP_OVERLAY_SET_ATTRIBUTES);
   1157         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1158         intent.putExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID, mediaItemId);
   1159         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, overlayId);
   1160         intent.putExtra(PARAM_ATTRIBUTES, userAttributes);
   1161 
   1162         startCommand(context, intent);
   1163     }
   1164 
   1165     /**
   1166      * Add an audio track
   1167      *
   1168      * @param context The context
   1169      * @param projectPath The project path
   1170      * @param id The audio track id
   1171      * @param uri The audio track URI
   1172      * @param loop true to loop the audio track
   1173      */
   1174     public static void addAudioTrack(Context context, String projectPath, String id, Uri uri,
   1175             boolean loop) {
   1176         final Intent intent = mIntentPool.get(context, ApiService.class);
   1177         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_ADD);
   1178         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1179         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, id);
   1180         intent.putExtra(PARAM_FILENAME, uri);
   1181         intent.putExtra(PARAM_LOOP, loop);
   1182 
   1183         startCommand(context, intent);
   1184     }
   1185 
   1186     /**
   1187      * Remove an audio track from the storyboard timeline
   1188      *
   1189      * @param context The context
   1190      * @param projectPath The project path
   1191      * @param audioTrackId The id of the audio track to remove
   1192      */
   1193     public static void removeAudioTrack(Context context, String projectPath, String audioTrackId) {
   1194         final Intent intent = mIntentPool.get(context, ApiService.class);
   1195         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_REMOVE);
   1196         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1197         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId);
   1198 
   1199         startCommand(context, intent);
   1200     }
   1201 
   1202     /**
   1203      * Set the audio track boundaries
   1204      *
   1205      * @param context The context
   1206      * @param projectPath The project path
   1207      * @param audioTrackId The id of the audio track
   1208      * @param beginBoundaryMs The audio track begin boundary
   1209      * @param endBoundaryMs The audio track end boundary
   1210      */
   1211     public static void setAudioTrackBoundaries(Context context, String projectPath,
   1212             String audioTrackId, long beginBoundaryMs, long endBoundaryMs) {
   1213         final Intent intent = mIntentPool.get(context, ApiService.class);
   1214         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_BOUNDARIES);
   1215         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1216         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId);
   1217         intent.putExtra(PARAM_BEGIN_BOUNDARY, beginBoundaryMs);
   1218         intent.putExtra(PARAM_END_BOUNDARY, endBoundaryMs);
   1219 
   1220         startCommand(context, intent);
   1221     }
   1222 
   1223     /**
   1224      * Set the loop flag for an audio track
   1225      *
   1226      * @param context The context
   1227      * @param projectPath The project path
   1228      * @param audioTrackId The id of the audio track
   1229      * @param loop true to loop audio
   1230      */
   1231     public static void setAudioTrackLoop(Context context, String projectPath, String audioTrackId,
   1232             boolean loop) {
   1233         final Intent intent = mIntentPool.get(context, ApiService.class);
   1234         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_LOOP);
   1235         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1236         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId);
   1237         intent.putExtra(PARAM_LOOP, loop);
   1238 
   1239         startCommand(context, intent);
   1240     }
   1241 
   1242     /**
   1243      * Set the duck flag for an audio track
   1244      *
   1245      * @param context The context
   1246      * @param projectPath The project path
   1247      * @param audioTrackId The id of the audio track
   1248      * @param duck true to enable ducking
   1249      */
   1250     public static void setAudioTrackDuck(Context context, String projectPath, String audioTrackId,
   1251             boolean duck) {
   1252         final Intent intent = mIntentPool.get(context, ApiService.class);
   1253         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_DUCK);
   1254         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1255         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId);
   1256         intent.putExtra(PARAM_DUCK, duck);
   1257 
   1258         startCommand(context, intent);
   1259     }
   1260 
   1261     /**
   1262      * Set the audio track volume
   1263      *
   1264      * @param context The context
   1265      * @param projectPath The project path
   1266      * @param audioTrackId The id of the audio track
   1267      * @param volumePercentage The audio track volume (in percentage)
   1268      */
   1269     public static void setAudioTrackVolume(Context context, String projectPath,
   1270             String audioTrackId, int volumePercentage) {
   1271         final Intent intent = mIntentPool.get(context, ApiService.class);
   1272         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_VOLUME);
   1273         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1274         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId);
   1275         intent.putExtra(PARAM_VOLUME, volumePercentage);
   1276 
   1277         startCommand(context, intent);
   1278     }
   1279 
   1280     /**
   1281      * Mute/unmute the audio track
   1282      *
   1283      * @param context The context
   1284      * @param projectPath The project path
   1285      * @param audioTrackId The id of the audio track
   1286      * @param muted true to mute the audio track
   1287      */
   1288     public static void setAudioTrackMute(Context context, String projectPath, String audioTrackId,
   1289             boolean muted) {
   1290         final Intent intent = mIntentPool.get(context, ApiService.class);
   1291         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_SET_MUTE);
   1292         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1293         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId);
   1294         intent.putExtra(PARAM_MUTE, muted);
   1295 
   1296         startCommand(context, intent);
   1297     }
   1298 
   1299     /**
   1300      * Extract the audio track audio waveform
   1301      *
   1302      * @param context The context
   1303      * @param projectPath The project path
   1304      * @param audioTrackId The id of the audio track
   1305      */
   1306     public static void extractAudioTrackAudioWaveform(Context context, String projectPath,
   1307             String audioTrackId) {
   1308         if (isAudioTrackAudioWaveformPending(projectPath, audioTrackId)) {
   1309             return;
   1310         }
   1311         final Intent intent = mIntentPool.get(context, ApiService.class);
   1312         intent.putExtra(PARAM_OP, OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM);
   1313         intent.putExtra(PARAM_PROJECT_PATH, projectPath);
   1314         intent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrackId);
   1315 
   1316         startCommand(context, intent);
   1317     }
   1318 
   1319     /**
   1320      * Check if extract audio waveform is pending for the specified audio track
   1321      *
   1322      * @param projectPath The project path
   1323      * @param audioTrackId The audio track id
   1324      *
   1325      * @return true if the extract audio waveform is pending
   1326      */
   1327     public static boolean isAudioTrackAudioWaveformPending(String projectPath,
   1328             String audioTrackId) {
   1329         for (Intent intent : mPendingIntents.values()) {
   1330             int op = intent.getIntExtra(PARAM_OP, -1);
   1331             if (op == OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM) {
   1332                 String pp = intent.getStringExtra(PARAM_PROJECT_PATH);
   1333                 if (pp.equals(projectPath)) {
   1334                     String mid = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   1335                     if (mid.equals(audioTrackId)) {
   1336                         return true;
   1337                     }
   1338                 }
   1339             }
   1340         }
   1341 
   1342         return false;
   1343     }
   1344 
   1345     /**
   1346      * Start the service (if it is not running) with the specified Intent
   1347      *
   1348      * @param context The context
   1349      * @param intent The intent
   1350      *
   1351      * @return The request id of the pending request
   1352      */
   1353     private static String startCommand(Context context, Intent intent) {
   1354         final String requestId = StringUtils.randomString(8);
   1355         intent.putExtra(PARAM_REQUEST_ID, requestId);
   1356         mPendingIntents.put(requestId, intent);
   1357 
   1358         context.startService(intent);
   1359 
   1360         final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH);
   1361         if (projectPath != null) {
   1362             final boolean projectEdited = isProjectBeingEdited(projectPath);
   1363             if (projectEdited) {
   1364                 for (ApiServiceListener listener : mListeners) {
   1365                     listener.onProjectEditState(projectPath, projectEdited);
   1366                 }
   1367             }
   1368         }
   1369 
   1370         return requestId;
   1371     }
   1372 
   1373     @Override
   1374     public void onCreate() {
   1375         super.onCreate();
   1376         mHandler = new Handler(Looper.getMainLooper());
   1377 
   1378         mVideoThread = new IntentProcessor("VideoServiceThread");
   1379         mVideoThread.start();
   1380 
   1381         mAudioThread = new IntentProcessor("AudioServiceThread");
   1382         mAudioThread.start();
   1383 
   1384         mThumbnailThread = new IntentProcessor("ThumbnailServiceThread");
   1385         mThumbnailThread.start();
   1386     }
   1387 
   1388     @Override
   1389     public int onStartCommand(final Intent intent, int flags, int startId) {
   1390         final int op = intent.getIntExtra(PARAM_OP, -1);
   1391         switch(op) {
   1392             case OP_VIDEO_EDITOR_LOAD_PROJECTS:
   1393             case OP_VIDEO_EDITOR_CREATE:
   1394             case OP_VIDEO_EDITOR_LOAD:
   1395             case OP_VIDEO_EDITOR_SAVE:
   1396             case OP_VIDEO_EDITOR_RELEASE:
   1397             case OP_VIDEO_EDITOR_DELETE:
   1398             case OP_VIDEO_EDITOR_SET_ASPECT_RATIO:
   1399             case OP_VIDEO_EDITOR_APPLY_THEME:
   1400             case OP_VIDEO_EDITOR_EXPORT:
   1401             case OP_VIDEO_EDITOR_CANCEL_EXPORT:
   1402             case OP_VIDEO_EDITOR_EXPORT_STATUS:
   1403 
   1404             case OP_MEDIA_ITEM_ADD_VIDEO_URI:
   1405             case OP_MEDIA_ITEM_ADD_IMAGE_URI:
   1406             case OP_MEDIA_ITEM_MOVE:
   1407             case OP_MEDIA_ITEM_REMOVE:
   1408             case OP_MEDIA_ITEM_SET_RENDERING_MODE:
   1409             case OP_MEDIA_ITEM_SET_DURATION:
   1410             case OP_MEDIA_ITEM_SET_BOUNDARIES:
   1411             case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM:
   1412             case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS:
   1413             case OP_MEDIA_ITEM_LOAD:
   1414             case OP_MEDIA_ITEM_LOAD_STATUS:
   1415 
   1416             case OP_EFFECT_ADD_COLOR:
   1417             case OP_EFFECT_ADD_IMAGE_KEN_BURNS:
   1418             case OP_EFFECT_REMOVE:
   1419 
   1420             case OP_TRANSITION_INSERT_ALPHA:
   1421             case OP_TRANSITION_INSERT_CROSSFADE:
   1422             case OP_TRANSITION_INSERT_FADE_BLACK:
   1423             case OP_TRANSITION_INSERT_SLIDING:
   1424             case OP_TRANSITION_REMOVE:
   1425             case OP_TRANSITION_SET_DURATION:
   1426 
   1427             case OP_OVERLAY_ADD:
   1428             case OP_OVERLAY_REMOVE:
   1429             case OP_OVERLAY_SET_START_TIME:
   1430             case OP_OVERLAY_SET_DURATION:
   1431             case OP_OVERLAY_SET_ATTRIBUTES:
   1432 
   1433             case OP_AUDIO_TRACK_ADD:
   1434             case OP_AUDIO_TRACK_REMOVE:
   1435             case OP_AUDIO_TRACK_SET_BOUNDARIES:
   1436             case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM:
   1437             case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS: {
   1438                 mVideoThread.submit(intent);
   1439                 break;
   1440             }
   1441 
   1442             case OP_TRANSITION_GET_THUMBNAIL: {
   1443                 mThumbnailThread.submit(intent);
   1444                 break;
   1445             }
   1446 
   1447             case OP_MEDIA_ITEM_GET_THUMBNAILS: {
   1448                 final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH);
   1449                 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   1450                 final int token = intent.getIntExtra(PARAM_TOKEN, 0);
   1451                 // Cancel any pending thumbnail request for the same media item
   1452                 // but with a different token
   1453                 Iterator<Intent> intentQueueIterator = mThumbnailThread.getIntentQueueIterator();
   1454                 while (intentQueueIterator.hasNext()) {
   1455                     Intent qIntent = intentQueueIterator.next();
   1456                     int opi = qIntent.getIntExtra(PARAM_OP, -1);
   1457                     String pp = qIntent.getStringExtra(PARAM_PROJECT_PATH);
   1458                     String mid = qIntent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   1459                     int tk = qIntent.getIntExtra(PARAM_TOKEN, 0);
   1460                     if (opi == op && pp.equals(projectPath) && mid.equals(mediaItemId)
   1461                             && tk != token) {
   1462                         boolean canceled = mThumbnailThread.cancel(qIntent);
   1463                         if (canceled) {
   1464                             logd("Canceled operation: " + op + " for media item" + mediaItemId);
   1465                             mPendingIntents.remove(qIntent.getStringExtra(PARAM_REQUEST_ID));
   1466                             mIntentPool.put(qIntent);
   1467                         }
   1468                         break;
   1469                     }
   1470                 }
   1471                 mThumbnailThread.submit(intent);
   1472                 break;
   1473             }
   1474 
   1475             case OP_MEDIA_ITEM_SET_VOLUME:
   1476             case OP_MEDIA_ITEM_SET_MUTE:
   1477 
   1478             case OP_AUDIO_TRACK_SET_VOLUME:
   1479             case OP_AUDIO_TRACK_SET_MUTE:
   1480             case OP_AUDIO_TRACK_SET_LOOP:
   1481             case OP_AUDIO_TRACK_SET_DUCK: {
   1482                 mAudioThread.submit(intent);
   1483                 break;
   1484             }
   1485 
   1486             default: {
   1487                 Log.e(TAG, "No thread assigned: " + op);
   1488                 break;
   1489             }
   1490         }
   1491 
   1492         return START_NOT_STICKY;
   1493     }
   1494 
   1495     @Override
   1496     public void onDestroy() {
   1497         super.onDestroy();
   1498 
   1499         if (mThumbnailThread != null) {
   1500             mThumbnailThread.quit();
   1501             mThumbnailThread = null;
   1502         }
   1503 
   1504         if (mAudioThread != null) {
   1505             mAudioThread.quit();
   1506             mAudioThread = null;
   1507         }
   1508 
   1509         if (mVideoThread != null) {
   1510             mVideoThread.quit();
   1511             mVideoThread = null;
   1512         }
   1513     }
   1514 
   1515     @Override
   1516     public IBinder onBind(Intent intent) {
   1517         return null;
   1518     }
   1519 
   1520     /**
   1521      * Process the intent
   1522      *
   1523      * @param intent The intent
   1524      */
   1525     public void processIntent(final Intent intent) {
   1526         final int op = intent.getIntExtra(PARAM_OP, -1);
   1527         VideoEditor videoEditor = null;
   1528         try {
   1529             final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH);
   1530             // Check if the project path matches the current VideoEditor project
   1531             switch (op) {
   1532                 case OP_VIDEO_EDITOR_LOAD_PROJECTS:
   1533                 case OP_VIDEO_EDITOR_CREATE:
   1534                 case OP_VIDEO_EDITOR_LOAD:
   1535                 case OP_VIDEO_EDITOR_DELETE: {
   1536                     break;
   1537                 }
   1538 
   1539                 default: {
   1540                     videoEditor = getVideoEditor(projectPath);
   1541                     if (videoEditor == null) {
   1542                         throw new IllegalArgumentException("Invalid project path: "
   1543                                 + projectPath + " for operation: " + op);
   1544                     }
   1545                     break;
   1546                 }
   1547             }
   1548 
   1549             switch (op) {
   1550                 case OP_VIDEO_EDITOR_LOAD_PROJECTS: {
   1551                     logd("OP_LOAD_PROJECTS");
   1552                     final List<VideoEditorProject> projects = new ArrayList<VideoEditorProject>();
   1553                     final File dir = FileUtils.getProjectsRootDir(getApplicationContext());
   1554                     if (dir != null) {
   1555                         // Collect valid projects (project with valid metadata).
   1556                         final File[] files = dir.listFiles();
   1557                         if (files != null) {
   1558                             for (int i = 0; i < files.length; i++) {
   1559                                 if (files[i].isDirectory()) {
   1560                                     final String pp = files[i].getAbsolutePath();
   1561                                     try {
   1562                                         projects.add(VideoEditorProject.fromXml(null, pp));
   1563                                     } catch (FileNotFoundException ex) {
   1564                                         Log.w(TAG, "processIntent: Project file not found: " + pp);
   1565                                         FileUtils.deleteDir(new File(pp));
   1566                                     } catch (Exception ex) {
   1567                                         ex.printStackTrace();
   1568                                     }
   1569                                 }
   1570                             }
   1571 
   1572                             if (projects.size() > 0) {
   1573                                 // Sort the projects in order of "last saved"
   1574                                 Collections.sort(projects, new Comparator<VideoEditorProject>() {
   1575                                     @Override
   1576                                     public int compare(VideoEditorProject object1,
   1577                                             VideoEditorProject object2) {
   1578                                         if (object1.getLastSaved() > object2.getLastSaved()) {
   1579                                             return -1;
   1580                                         } else if (object1.getLastSaved() == object2.getLastSaved()) {
   1581                                             return 0;
   1582                                         } else {
   1583                                             return 1;
   1584                                         }
   1585                                     }
   1586                                 });
   1587                             }
   1588                         }
   1589                     }
   1590 
   1591                     completeRequest(intent, videoEditor, null, projects, null, true);
   1592                     break;
   1593                 }
   1594 
   1595                 case OP_VIDEO_EDITOR_CREATE: {
   1596                     logd("OP_VIDEO_EDITOR_CREATE: " + projectPath);
   1597 
   1598                     try {
   1599                         // Release the current video editor if any
   1600                         releaseEditor();
   1601 
   1602                         videoEditor = VideoEditorFactory.create(projectPath);
   1603 
   1604                         // Add the movies to the timeline
   1605                         final String[] movies = intent.getStringArrayExtra(PARAM_MOVIES_FILENAMES);
   1606                         for (int i = 0; i < movies.length; i++) {
   1607                             final MediaItem mediaItem = new MediaVideoItem(videoEditor,
   1608                                     generateId(), movies[i],
   1609                                     MediaItem.RENDERING_MODE_BLACK_BORDER);
   1610                             videoEditor.addMediaItem(mediaItem);
   1611                         }
   1612 
   1613                         // Add the photos to the timeline
   1614                         final String[] photos = intent.getStringArrayExtra(PARAM_PHOTOS_FILENAMES);
   1615                         for (int i = 0; i < photos.length; i++) {
   1616                             final MediaItem mediaItem = new MediaImageItem(videoEditor,
   1617                                     generateId(), photos[i],
   1618                                     MediaItemUtils.getDefaultImageDuration(),
   1619                                     MediaItem.RENDERING_MODE_BLACK_BORDER);
   1620                             videoEditor.addMediaItem(mediaItem);
   1621                         }
   1622 
   1623                         // Create the project
   1624                         final String projectName = intent.getStringExtra(PARAM_PROJECT_NAME);
   1625                         final String themeId = intent.getStringExtra(PARAM_THEME);
   1626                         if (themeId != null) {
   1627                             applyThemeToMovie(videoEditor, themeId);
   1628                         }
   1629 
   1630                         // Set the aspect ratio to the aspect ratio of the first item
   1631                         final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   1632                         if (mediaItems.size() > 0) {
   1633                             videoEditor.setAspectRatio(mediaItems.get(0).getAspectRatio());
   1634                         }
   1635 
   1636                         // Create the video editor project
   1637                         final VideoEditorProject videoProject = new VideoEditorProject(
   1638                                 videoEditor, projectPath, projectName, System.currentTimeMillis(),
   1639                                 0, 0, VideoEditorProject.DEFAULT_ZOOM_LEVEL, null, themeId, null);
   1640                         videoProject.setMediaItems(copyMediaItems(
   1641                                 videoEditor.getAllMediaItems()));
   1642                         videoProject.setAudioTracks(copyAudioTracks(
   1643                                 videoEditor.getAllAudioTracks()));
   1644 
   1645                         // Make this project the current project
   1646                         mVideoEditor = videoEditor;
   1647                         mGeneratePreviewListener = new ServiceMediaProcessingProgressListener(
   1648                                 projectPath);
   1649 
   1650                         completeRequest(intent, videoEditor, null, videoProject, null, false);
   1651                         generatePreview(videoEditor, true);
   1652                         completeRequest(intent);
   1653                     } catch (Exception ex) {
   1654                         if (videoEditor != null) {
   1655                             videoEditor.release();
   1656                             videoEditor = null;
   1657                         }
   1658                         throw ex;
   1659                     }
   1660 
   1661                     break;
   1662                 }
   1663 
   1664                 case OP_VIDEO_EDITOR_LOAD: {
   1665                     videoEditor = releaseEditorNot(projectPath);
   1666 
   1667                     if (videoEditor == null) {  // The old project was released.
   1668                         logd("OP_VIDEO_EDITOR_LOAD: Loading: " + projectPath);
   1669                         try {
   1670                             // Load the project
   1671                             videoEditor = VideoEditorFactory.load(projectPath, false);
   1672 
   1673                             // Load the video editor project
   1674                             final VideoEditorProject videoProject = VideoEditorProject.fromXml(
   1675                                     videoEditor, projectPath);
   1676                             videoProject.setMediaItems(copyMediaItems(
   1677                                     videoEditor.getAllMediaItems()));
   1678                             videoProject.setAudioTracks(copyAudioTracks(
   1679                                     videoEditor.getAllAudioTracks()));
   1680                             // Make this the current project
   1681                             mVideoEditor = videoEditor;
   1682                             mGeneratePreviewListener = new ServiceMediaProcessingProgressListener(
   1683                                     projectPath);
   1684 
   1685                             completeRequest(intent, videoEditor, null, videoProject, null, false);
   1686                             generatePreview(videoEditor, true);
   1687                             completeRequest(intent);
   1688                         } catch (Exception ex) {
   1689                             if (videoEditor != null) {
   1690                                 videoEditor.release();
   1691                                 videoEditor = null;
   1692                             }
   1693                             throw ex;
   1694                         }
   1695                     } else {  // The project is already loaded.
   1696                         logd("OP_VIDEO_EDITOR_LOAD: Was already loaded: " + projectPath);
   1697                         completeRequest(intent, videoEditor, null, null, null, true);
   1698                     }
   1699 
   1700                     break;
   1701                 }
   1702 
   1703                 case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: {
   1704                     logd("OP_VIDEO_EDITOR_SET_ASPECT_RATIO");
   1705 
   1706                     videoEditor.setAspectRatio(intent.getIntExtra(PARAM_ASPECT_RATIO,
   1707                             MediaProperties.ASPECT_RATIO_UNDEFINED));
   1708 
   1709                     completeRequest(intent, videoEditor, null, null, null, false);
   1710                     generatePreview(videoEditor, true);
   1711                     completeRequest(intent);
   1712                     break;
   1713                 }
   1714 
   1715                 case OP_VIDEO_EDITOR_APPLY_THEME: {
   1716                     logd("OP_VIDEO_EDITOR_APPLY_THEME");
   1717 
   1718                     // Apply the theme
   1719                     applyThemeToMovie(videoEditor, intent.getStringExtra(PARAM_THEME));
   1720 
   1721                     final List<MovieMediaItem> mediaItems =
   1722                             copyMediaItems(videoEditor.getAllMediaItems());
   1723                     final List<MovieAudioTrack> audioTracks =
   1724                             copyAudioTracks(videoEditor.getAllAudioTracks());
   1725 
   1726                     completeRequest(intent, videoEditor, null, mediaItems, audioTracks, false);
   1727                     generatePreview(videoEditor, true);
   1728                     completeRequest(intent);
   1729                     break;
   1730                 }
   1731 
   1732                 case OP_VIDEO_EDITOR_EXPORT: {
   1733                     logd("OP_VIDEO_EDITOR_EXPORT");
   1734                     exportMovie(videoEditor, intent);
   1735                     break;
   1736                 }
   1737 
   1738                 case OP_VIDEO_EDITOR_CANCEL_EXPORT: {
   1739                     logd("OP_VIDEO_EDITOR_CANCEL_EXPORT");
   1740                     videoEditor.cancelExport(intent.getStringExtra(PARAM_FILENAME));
   1741                     completeRequest(intent, videoEditor, null, null, null, true);
   1742                     break;
   1743                 }
   1744 
   1745                 case OP_VIDEO_EDITOR_EXPORT_STATUS: {
   1746                     logd("OP_VIDEO_EDITOR_EXPORT_STATUS");
   1747                     completeRequest(intent, videoEditor, null, null, null, true);
   1748                     break;
   1749                 }
   1750 
   1751                 case OP_VIDEO_EDITOR_SAVE: {
   1752                     logd("OP_VIDEO_EDITOR_SAVE: " + projectPath);
   1753                     videoEditor.save();
   1754 
   1755                     final VideoEditorProject videoProject = getProject(projectPath);
   1756                     if (videoProject != null) {
   1757                         videoProject.saveToXml();
   1758                     }
   1759 
   1760                     completeRequest(intent, videoEditor, null, null, null, true);
   1761                     break;
   1762                 }
   1763 
   1764                 case OP_VIDEO_EDITOR_RELEASE: {
   1765                     logd("OP_VIDEO_EDITOR_RELEASE: " + projectPath);
   1766                     releaseEditor(projectPath);
   1767                     completeRequest(intent, videoEditor, null, null, null, true);
   1768                     break;
   1769                 }
   1770 
   1771                 case OP_VIDEO_EDITOR_DELETE: {
   1772                     logd("OP_VIDEO_EDITOR_DELETE: " + projectPath);
   1773                     releaseEditor(projectPath);
   1774                     // Delete all the files and the project folder.
   1775                     FileUtils.deleteDir(new File(projectPath));
   1776                     completeRequest(intent, videoEditor, null, null, null, true);
   1777                     break;
   1778                 }
   1779 
   1780                 case OP_MEDIA_ITEM_ADD_VIDEO_URI: {
   1781                     logd("OP_MEDIA_ITEM_ADD_VIDEO_URI: " +
   1782                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   1783                     final Uri data = intent.getParcelableExtra(PARAM_FILENAME);
   1784                     String filename = null;
   1785                     // Get the filename
   1786                     Cursor cursor = null;
   1787                     try {
   1788                         cursor = getContentResolver().query(data,
   1789                                 new String[] {Video.Media.DATA}, null, null, null);
   1790                         if (cursor.moveToFirst()) {
   1791                             filename = cursor.getString(0);
   1792                         }
   1793                     } finally {
   1794                         if (cursor != null) {
   1795                             cursor.close();
   1796                         }
   1797                     }
   1798 
   1799                     if (filename == null) {
   1800                         throw new IllegalArgumentException("Media file not found: " + data);
   1801                     }
   1802 
   1803                     final MediaItem mediaItem = new MediaVideoItem(videoEditor,
   1804                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   1805                             filename,
   1806                             intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 0));
   1807 
   1808                     videoEditor.insertMediaItem(mediaItem,
   1809                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   1810 
   1811                     // If this is the first media item, change the aspect ratio
   1812                     final Integer aspectRatio;
   1813                     if (videoEditor.getAllMediaItems().size() == 1) {
   1814                         videoEditor.setAspectRatio(mediaItem.getAspectRatio());
   1815                         aspectRatio = videoEditor.getAspectRatio();
   1816                     } else {
   1817                         aspectRatio = null;
   1818                     }
   1819 
   1820                     // Apply the theme if any
   1821                     final String themeId = intent.getStringExtra(PARAM_THEME);
   1822                     if (themeId != null) {
   1823                         applyThemeToMediaItem(videoEditor, themeId, mediaItem);
   1824                     }
   1825 
   1826                     completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem),
   1827                             aspectRatio, false);
   1828                     generatePreview(videoEditor, true);
   1829                     completeRequest(intent);
   1830                     break;
   1831                 }
   1832 
   1833                 case OP_MEDIA_ITEM_ADD_IMAGE_URI: {
   1834                     logd("OP_MEDIA_ITEM_ADD_IMAGE_URI: "
   1835                         + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   1836 
   1837                     final Uri data = intent.getParcelableExtra(PARAM_FILENAME);
   1838                     String filename = null;
   1839                     // Get the filename
   1840                     Cursor cursor = null;
   1841                     try {
   1842                         cursor = getContentResolver().query(data,
   1843                                 new String[] {Images.Media.DATA, Images.Media.MIME_TYPE},
   1844                                 null, null, null);
   1845                         if (cursor.moveToFirst()) {
   1846                             filename = cursor.getString(0);
   1847                             final String mimeType = cursor.getString(1);
   1848                             if ("image/jpeg".equals(mimeType)) {
   1849                                 try {
   1850                                     final File outputFile = new File(projectPath,
   1851                                             "gallery_image_" + generateId() + ".jpg");
   1852                                     if (ImageUtils.transformJpeg(filename, outputFile)) {
   1853                                         filename = outputFile.getAbsolutePath();
   1854                                     }
   1855                                 } catch (Exception ex) {
   1856                                     // Ignore the exception and continue
   1857                                     Log.w(TAG, "Could not transform JPEG: " + filename, ex);
   1858                                 }
   1859                             }
   1860                         }
   1861                     } finally {
   1862                         if (cursor != null) {
   1863                             cursor.close();
   1864                         }
   1865                     }
   1866 
   1867                     if (filename == null) {
   1868                         throw new IllegalArgumentException("Media file not found: " + data);
   1869                     }
   1870 
   1871                     final MediaItem mediaItem = new MediaImageItem(videoEditor,
   1872                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   1873                             filename,
   1874                             intent.getLongExtra(PARAM_DURATION, 0),
   1875                             intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE, 0));
   1876 
   1877                     videoEditor.insertMediaItem(mediaItem,
   1878                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   1879 
   1880                     // If this is the first media item, change the aspect ratio
   1881                     final Integer aspectRatio;
   1882                     if (videoEditor.getAllMediaItems().size() == 1) {
   1883                         videoEditor.setAspectRatio(mediaItem.getAspectRatio());
   1884                         aspectRatio = videoEditor.getAspectRatio();
   1885                     } else {
   1886                         aspectRatio = null;
   1887                     }
   1888 
   1889                     // Apply the theme if any
   1890                     final String themeId = intent.getStringExtra(PARAM_THEME);
   1891                     if (themeId != null) {
   1892                         applyThemeToMediaItem(videoEditor, themeId, mediaItem);
   1893                     }
   1894 
   1895                     completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem),
   1896                             aspectRatio, false);
   1897                     generatePreview(videoEditor, true);
   1898                     completeRequest(intent);
   1899                     break;
   1900                 }
   1901 
   1902                 case OP_MEDIA_ITEM_LOAD: {
   1903                     final Uri data = intent.getParcelableExtra(PARAM_FILENAME);
   1904                     logd("OP_MEDIA_ITEM_LOAD: " + data);
   1905                     final Intent requestIntent = intent;
   1906                     new Thread() {
   1907                         @Override
   1908                         public void run() {
   1909                             InputStream is = null;
   1910                             FileOutputStream fos = null;
   1911                             final File file = new File(projectPath, "download_" + generateId());
   1912 
   1913                             final Intent statusIntent = mIntentPool.get();
   1914                             statusIntent.putExtra(PARAM_OP, OP_MEDIA_ITEM_LOAD_STATUS);
   1915                             statusIntent.putExtra(PARAM_PROJECT_PATH,
   1916                                     requestIntent.getStringExtra(PARAM_PROJECT_PATH));
   1917                             statusIntent.putExtra(PARAM_INTENT, requestIntent);
   1918                             try {
   1919                                 is = getContentResolver().openInputStream(data);
   1920                                 // Save the input stream to a file
   1921                                 fos = new FileOutputStream(file);
   1922                                 final byte[] readBuffer = new byte[2048];
   1923                                 int readBytes;
   1924                                 while ((readBytes = is.read(readBuffer)) >= 0) {
   1925                                     fos.write(readBuffer, 0, readBytes);
   1926                                 }
   1927                             } catch (Exception ex) {
   1928                                 Log.e(TAG, "Cannot open input stream for: " + data);
   1929                                 statusIntent.putExtra(PARAM_EXCEPTION, ex);
   1930                                 file.delete();
   1931                             } finally {
   1932                                 if (is != null) {
   1933                                     try {
   1934                                         is.close();
   1935                                     } catch (IOException ex) {
   1936                                         Log.e(TAG, "Cannot close input stream for: " + data);
   1937                                     }
   1938                                 }
   1939 
   1940                                 if (fos != null) {
   1941                                     try {
   1942                                         fos.flush();
   1943                                         fos.close();
   1944                                     } catch (IOException ex) {
   1945                                         Log.e(TAG, "Cannot close output stream for: " + data);
   1946                                     }
   1947                                 }
   1948                             }
   1949 
   1950                             if (!statusIntent.hasExtra(PARAM_EXCEPTION)) {
   1951                                 final String filename = file.getAbsolutePath();
   1952                                 try {
   1953                                     final String mimeType = getContentResolver().getType(data);
   1954                                     if ("image/jpeg".equals(mimeType)) {
   1955                                         final File outputFile = new File(projectPath,
   1956                                                 "download_" + generateId() + ".jpg");
   1957                                         if (ImageUtils.transformJpeg(filename, outputFile)) {
   1958                                             // Delete the downloaded file
   1959                                             file.delete();
   1960                                             statusIntent.putExtra(PARAM_FILENAME,
   1961                                                     outputFile.getAbsolutePath());
   1962                                         } else {
   1963                                             statusIntent.putExtra(PARAM_FILENAME, filename);
   1964                                         }
   1965                                     } else {
   1966                                         statusIntent.putExtra(PARAM_FILENAME, filename);
   1967                                     }
   1968                                 } catch (Exception ex) {
   1969                                     // Ignore the exception and continue
   1970                                     Log.w(TAG, "Could not transform JPEG: " + filename, ex);
   1971                                     statusIntent.putExtra(PARAM_FILENAME, filename);
   1972                                 }
   1973                             }
   1974 
   1975                             mVideoThread.submit(statusIntent);
   1976                         }
   1977                     }.start();
   1978 
   1979                     break;
   1980                 }
   1981 
   1982                 case OP_MEDIA_ITEM_LOAD_STATUS: {
   1983                     final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT);
   1984                     if (intent.hasExtra(PARAM_EXCEPTION)) { //
   1985                         final Exception exception =
   1986                             (Exception)intent.getSerializableExtra(PARAM_EXCEPTION);
   1987                         completeRequest(intent, videoEditor, exception, null, originalIntent,
   1988                                 true);
   1989                     } else {
   1990                         completeRequest(intent, videoEditor, null,
   1991                                 intent.getStringExtra(PARAM_FILENAME), originalIntent, true);
   1992                     }
   1993                     break;
   1994                 }
   1995 
   1996                 case OP_MEDIA_ITEM_MOVE: {
   1997                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   1998                     logd("OP_MEDIA_ITEM_MOVE: " + mediaItemId);
   1999 
   2000                     // Determine the position of the media item we are moving
   2001                     final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   2002                     final int mediaItemsCount = mediaItems.size();
   2003                     int movedItemPosition = -1;
   2004                     MediaItem movedMediaItem = null;
   2005                     for (int i = 0; i < mediaItemsCount; i++) {
   2006                         final MediaItem mi = mediaItems.get(i);
   2007                         if (mi.getId().equals(mediaItemId)) {
   2008                             movedMediaItem = mi;
   2009                             movedItemPosition = i;
   2010                             break;
   2011                         }
   2012                     }
   2013 
   2014                     if (movedItemPosition == -1) {
   2015                         throw new IllegalArgumentException("Moved MediaItem not found: " +
   2016                                 mediaItemId);
   2017                     }
   2018 
   2019                     final Transition beginTransition = movedMediaItem.getBeginTransition();
   2020                     final Transition endTransition = movedMediaItem.getEndTransition();
   2021 
   2022                     final String afterMediaItemId = intent.getStringExtra(
   2023                             PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   2024                     videoEditor.moveMediaItem(mediaItemId, afterMediaItemId);
   2025 
   2026                     // Apply the theme if any
   2027                     final String themeId = intent.getStringExtra(PARAM_THEME);
   2028                     if (themeId != null) {
   2029                         // Apply the theme at the removed position
   2030                         applyThemeAfterMove(videoEditor, themeId, movedMediaItem,
   2031                                 movedItemPosition, beginTransition, endTransition);
   2032                     }
   2033 
   2034                     final List<MovieMediaItem> mediaItemsCopy = copyMediaItems(mediaItems);
   2035                     completeRequest(intent, videoEditor, null, mediaItemsCopy, null, false);
   2036                     generatePreview(videoEditor, true);
   2037                     completeRequest(intent);
   2038                     break;
   2039                 }
   2040 
   2041                 case OP_MEDIA_ITEM_REMOVE: {
   2042                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2043                     logd("OP_MEDIA_ITEM_REMOVE: " + mediaItemId);
   2044 
   2045                     // Determine the position of the media item we are removing
   2046                     final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   2047                     final int mediaItemsCount = mediaItems.size();
   2048                     int removedItemPosition = -1;
   2049                     MediaItem removedMediaItem = null;
   2050                     for (int i = 0; i < mediaItemsCount; i++) {
   2051                         if (mediaItems.get(i).getId().equals(mediaItemId)) {
   2052                             removedMediaItem = mediaItems.get(i);
   2053                             removedItemPosition = i;
   2054                             break;
   2055                         }
   2056                     }
   2057 
   2058                     if (removedMediaItem == null) {
   2059                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2060                     }
   2061 
   2062                     final Transition beginTransition = removedMediaItem.getBeginTransition();
   2063                     final Transition endTransition = removedMediaItem.getEndTransition();
   2064 
   2065                     videoEditor.removeMediaItem(mediaItemId);
   2066 
   2067                     // Apply the theme if any
   2068                     MovieTransition movieTransition = null;
   2069                     final String themeId = intent.getStringExtra(PARAM_THEME);
   2070                     if (themeId != null && mediaItems.size() > 0) {
   2071                         final Transition transition = applyThemeAfterRemove(videoEditor, themeId,
   2072                                 removedItemPosition, beginTransition, endTransition);
   2073                         if (transition != null) {
   2074                             movieTransition = new MovieTransition(transition);
   2075                         }
   2076                     }
   2077 
   2078                     completeRequest(intent, videoEditor, null, movieTransition, null, false);
   2079                     generatePreview(videoEditor, true);
   2080                     completeRequest(intent);
   2081                     break;
   2082                 }
   2083 
   2084                 case OP_MEDIA_ITEM_SET_RENDERING_MODE: {
   2085                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2086                     logd("OP_MEDIA_ITEM_SET_RENDERING_MODE: " + mediaItemId);
   2087 
   2088                     final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId);
   2089                     if (mediaItem == null) {
   2090                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2091                     }
   2092                     mediaItem.setRenderingMode(intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE,
   2093                             MediaItem.RENDERING_MODE_BLACK_BORDER));
   2094 
   2095                     completeRequest(intent, videoEditor, null, null, null, false);
   2096                     generatePreview(videoEditor, true);
   2097                     completeRequest(intent);
   2098                     break;
   2099                 }
   2100 
   2101                 case OP_MEDIA_ITEM_SET_DURATION: {
   2102                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2103                     logd("OP_MEDIA_ITEM_SET_DURATION: " + mediaItemId);
   2104 
   2105                     final MediaImageItem mediaItem =
   2106                         (MediaImageItem)videoEditor.getMediaItem(mediaItemId);
   2107                     if (mediaItem == null) {
   2108                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2109                     }
   2110 
   2111                     final long durationMs = intent.getLongExtra(PARAM_DURATION, 0);
   2112                     mediaItem.setDuration(durationMs);
   2113                     // Adjust all effects to the new duration
   2114                     final List<Effect> effects = mediaItem.getAllEffects();
   2115                     for (Effect effect : effects) {
   2116                         effect.setDuration(durationMs);
   2117                     }
   2118 
   2119                     completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), null,
   2120                             false);
   2121                     generatePreview(videoEditor, true);
   2122                     completeRequest(intent);
   2123                     break;
   2124                 }
   2125 
   2126                 case OP_MEDIA_ITEM_SET_BOUNDARIES: {
   2127                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2128                     final MediaVideoItem mediaItem =
   2129                         (MediaVideoItem)videoEditor.getMediaItem(mediaItemId);
   2130                     if (mediaItem == null) {
   2131                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2132                     }
   2133 
   2134                     mediaItem.setExtractBoundaries(intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0),
   2135                             intent.getLongExtra(PARAM_END_BOUNDARY, 0));
   2136 
   2137                     final List<Overlay> overlays = mediaItem.getAllOverlays();
   2138                     if (overlays.size() > 0) {
   2139                         // Adjust the overlay
   2140                         final Overlay overlay = overlays.get(0);
   2141                         if (overlay.getStartTime() < mediaItem.getBoundaryBeginTime()) {
   2142                             overlay.setStartTime(mediaItem.getBoundaryBeginTime());
   2143                             overlay.setDuration(Math.min(overlay.getDuration(),
   2144                                     mediaItem.getTimelineDuration()));
   2145                         } else if (overlay.getStartTime() + overlay.getDuration() >
   2146                                     mediaItem.getBoundaryEndTime()) {
   2147                             overlay.setStartTime(Math.max(mediaItem.getBoundaryBeginTime(),
   2148                                     mediaItem.getBoundaryEndTime() - overlay.getDuration()));
   2149                             overlay.setDuration(mediaItem.getBoundaryEndTime() -
   2150                                     overlay.getStartTime());
   2151                         }
   2152                     }
   2153 
   2154                     completeRequest(intent, videoEditor, null, new MovieMediaItem(mediaItem), null,
   2155                             false);
   2156                     generatePreview(videoEditor, true);
   2157                     completeRequest(intent);
   2158                     break;
   2159                 }
   2160 
   2161                 case OP_MEDIA_ITEM_GET_THUMBNAILS: {
   2162                     // Note that this command is executed in the thumbnail thread
   2163                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2164                     logd("OP_MEDIA_ITEM_GET_THUMBNAILS: " + mediaItemId);
   2165 
   2166                     final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId);
   2167                     if (mediaItem == null) {
   2168                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2169                     }
   2170 
   2171                     final VideoEditor ve = videoEditor; // Just to make it "final"
   2172                     mediaItem.getThumbnailList(
   2173                             intent.getIntExtra(PARAM_WIDTH, 0),
   2174                             intent.getIntExtra(PARAM_HEIGHT, 0),
   2175                             intent.getLongExtra(PARAM_START_TIME, 0),
   2176                             intent.getLongExtra(PARAM_END_TIME, 0),
   2177                             intent.getIntExtra(PARAM_COUNT, 0),
   2178                             intent.getIntArrayExtra(PARAM_INDICES),
   2179                             new GetThumbnailListCallback() {
   2180                                 public void onThumbnail(Bitmap bitmap, int index) {
   2181                                     completeRequest(
   2182                                             intent, ve, null, bitmap,
   2183                                             Integer.valueOf(index), false);
   2184                                 }
   2185                             }
   2186                             );
   2187 
   2188                     completeRequest(intent, videoEditor, null, null, null, true);
   2189                     break;
   2190                 }
   2191 
   2192                 case OP_MEDIA_ITEM_SET_VOLUME: {
   2193                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2194                     logd("OP_MEDIA_ITEM_SET_VOLUME: " + mediaItemId);
   2195 
   2196                     final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId);
   2197                     if (mediaItem != null && mediaItem instanceof MediaVideoItem) {
   2198                         ((MediaVideoItem)mediaItem).setVolume(intent.getIntExtra(PARAM_VOLUME, 0));
   2199 
   2200                         completeRequest(intent, videoEditor, null, null, null, false);
   2201                         generatePreview(videoEditor, false);
   2202                         completeRequest(intent);
   2203                     } else {
   2204                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2205                     }
   2206                     break;
   2207                 }
   2208 
   2209                 case OP_MEDIA_ITEM_SET_MUTE: {
   2210                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2211                     logd("OP_MEDIA_ITEM_SET_MUTE: " + mediaItemId);
   2212 
   2213                     final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId);
   2214                     if (mediaItem != null && mediaItem instanceof MediaVideoItem) {
   2215                         ((MediaVideoItem)mediaItem).setMute(intent.getBooleanExtra(PARAM_MUTE,
   2216                                 false));
   2217 
   2218                         completeRequest(intent, videoEditor, null, null, null, false);
   2219                         generatePreview(videoEditor, false);
   2220                         completeRequest(intent);
   2221                     } else {
   2222                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2223                     }
   2224                     break;
   2225                 }
   2226 
   2227                 case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: {
   2228                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2229                     logd("OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: " + mediaItemId);
   2230 
   2231                     final MediaItem mediaItem = videoEditor.getMediaItem(mediaItemId);
   2232                     if (mediaItem != null && mediaItem instanceof MediaVideoItem) {
   2233                         final MediaVideoItem movieMediaItem = ((MediaVideoItem)mediaItem);
   2234                         final WaveformData waveformData = movieMediaItem.getWaveformData();
   2235                         if (waveformData == null) {
   2236                             extractMediaItemAudioWaveform(intent, videoEditor, movieMediaItem);
   2237                             completeRequest(intent, videoEditor, null,
   2238                                     movieMediaItem.getWaveformData(), null, true);
   2239                         } else {
   2240                             completeRequest(intent, videoEditor, null, waveformData, null, true);
   2241                         }
   2242                     } else {
   2243                         throw new IllegalArgumentException("MediaItem not found: " + mediaItemId);
   2244                     }
   2245                     break;
   2246                 }
   2247 
   2248                 case OP_TRANSITION_INSERT_ALPHA: {
   2249                     logd("OP_TRANSITION_INSERT_ALPHA: "
   2250                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2251 
   2252                     final String afterMediaItemId =
   2253                         intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   2254                     final MediaItem afterMediaItem;
   2255                     if (afterMediaItemId != null) {
   2256                         afterMediaItem = videoEditor.getMediaItem(afterMediaItemId);
   2257                     } else {
   2258                         afterMediaItem = null;
   2259                     }
   2260 
   2261                     final int maskRawResourceId = intent.getIntExtra(PARAM_TRANSITION_MASK,
   2262                             R.raw.mask_contour);
   2263 
   2264                     final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId);
   2265                     final Transition transition = new TransitionAlpha(
   2266                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   2267                             afterMediaItem, beforeMediaItem,
   2268                             intent.getLongExtra(PARAM_DURATION, 0),
   2269                             intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR,
   2270                                     Transition.BEHAVIOR_LINEAR),
   2271                                     FileUtils.getMaskFilename(getApplicationContext(),
   2272                                             maskRawResourceId),
   2273                             intent.getIntExtra(PARAM_TRANSITION_BLENDING, 100),
   2274                             intent.getBooleanExtra(PARAM_TRANSITION_INVERT, false));
   2275                     videoEditor.addTransition(transition);
   2276 
   2277                     completeRequest(intent, videoEditor, null, transition, null, false);
   2278                     generatePreview(videoEditor, true);
   2279                     completeRequest(intent);
   2280                     break;
   2281                 }
   2282 
   2283                 case OP_TRANSITION_INSERT_CROSSFADE: {
   2284                     logd("OP_TRANSITION_INSERT_CROSSFADE: "
   2285                         + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2286 
   2287                     final String afterMediaItemId =
   2288                         intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   2289                     final MediaItem afterMediaItem;
   2290                     if (afterMediaItemId != null) {
   2291                         afterMediaItem = videoEditor.getMediaItem(afterMediaItemId);
   2292                     } else {
   2293                         afterMediaItem = null;
   2294                     }
   2295 
   2296                     final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId);
   2297                     final Transition transition = new TransitionCrossfade(
   2298                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   2299                             afterMediaItem, beforeMediaItem,
   2300                             intent.getLongExtra(PARAM_DURATION, 0),
   2301                             intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR,
   2302                                     Transition.BEHAVIOR_LINEAR));
   2303                     videoEditor.addTransition(transition);
   2304 
   2305                     completeRequest(intent, videoEditor, null, transition, null, false);
   2306                     generatePreview(videoEditor, true);
   2307                     completeRequest(intent);
   2308                     break;
   2309                 }
   2310 
   2311                 case OP_TRANSITION_INSERT_FADE_BLACK: {
   2312                     logd("OP_TRANSITION_INSERT_FADE_TO_BLACK: "
   2313                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2314 
   2315                     final String afterMediaItemId =
   2316                         intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   2317                     final MediaItem afterMediaItem;
   2318                     if (afterMediaItemId != null) {
   2319                         afterMediaItem = videoEditor.getMediaItem(afterMediaItemId);
   2320                     } else {
   2321                         afterMediaItem = null;
   2322                     }
   2323 
   2324                     final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId);
   2325                     final Transition transition = new TransitionFadeBlack(
   2326                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   2327                             afterMediaItem, beforeMediaItem,
   2328                             intent.getLongExtra(PARAM_DURATION, 0),
   2329                             intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR,
   2330                                     Transition.BEHAVIOR_LINEAR));
   2331                     videoEditor.addTransition(transition);
   2332 
   2333                     completeRequest(intent, videoEditor, null, transition, null, false);
   2334                     generatePreview(videoEditor, true);
   2335                     completeRequest(intent);
   2336                     break;
   2337                 }
   2338 
   2339                 case OP_TRANSITION_INSERT_SLIDING: {
   2340                     logd("OP_TRANSITION_INSERT_SLIDING: "
   2341                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2342 
   2343                     final String afterMediaItemId =
   2344                         intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   2345                     final MediaItem afterMediaItem;
   2346                     if (afterMediaItemId != null) {
   2347                         afterMediaItem = videoEditor.getMediaItem(afterMediaItemId);
   2348                     } else {
   2349                         afterMediaItem = null;
   2350                     }
   2351 
   2352                     final MediaItem beforeMediaItem = nextMediaItem(videoEditor, afterMediaItemId);
   2353                     final Transition transition = new TransitionSliding(
   2354                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   2355                             afterMediaItem, beforeMediaItem,
   2356                             intent.getLongExtra(PARAM_DURATION, 0),
   2357                             intent.getIntExtra(PARAM_TRANSITION_BEHAVIOR,
   2358                                     Transition.BEHAVIOR_LINEAR),
   2359                                     intent.getIntExtra(PARAM_TRANSITION_DIRECTION,
   2360                                             TransitionSliding.DIRECTION_RIGHT_OUT_LEFT_IN));
   2361                     videoEditor.addTransition(transition);
   2362 
   2363                     completeRequest(intent, videoEditor, null, transition, null, false);
   2364                     generatePreview(videoEditor, true);
   2365                     completeRequest(intent);
   2366                     break;
   2367                 }
   2368 
   2369                 case OP_TRANSITION_REMOVE: {
   2370                     logd("OP_TRANSITION_REMOVE: "
   2371                         + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2372 
   2373                     videoEditor.removeTransition(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2374 
   2375                     completeRequest(intent, videoEditor, null, null, null, false);
   2376                     generatePreview(videoEditor, true);
   2377                     completeRequest(intent);
   2378                     break;
   2379                 }
   2380 
   2381                 case OP_TRANSITION_SET_DURATION: {
   2382                     final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2383                     logd("OP_TRANSITION_SET_DURATION: " + transitionId);
   2384 
   2385                     final Transition transition = videoEditor.getTransition(transitionId);
   2386                     if (transition == null) {
   2387                         throw new IllegalArgumentException("Transition not found: " +
   2388                                 transitionId);
   2389                     }
   2390                     transition.setDuration(intent.getLongExtra(PARAM_DURATION, 0));
   2391 
   2392                     completeRequest(intent, videoEditor, null, null, null, false);
   2393                     generatePreview(videoEditor, true);
   2394                     completeRequest(intent);
   2395                     break;
   2396                 }
   2397 
   2398                 case OP_TRANSITION_GET_THUMBNAIL: {
   2399                     final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2400                     logd("OP_TRANSITION_GET_THUMBNAIL: " + transitionId);
   2401 
   2402                     final Transition transition = videoEditor.getTransition(transitionId);
   2403                     if (transition == null) {
   2404                         throw new IllegalArgumentException("Transition not found: " +
   2405                                 transitionId);
   2406                     }
   2407 
   2408                     final int height = intent.getIntExtra(PARAM_HEIGHT, 0);
   2409                     final MediaItem afterMediaItem = transition.getAfterMediaItem();
   2410                     final Bitmap[] thumbnails = new Bitmap[2];
   2411                     if (afterMediaItem != null) {
   2412                         thumbnails[0] = afterMediaItem.getThumbnail(
   2413                                 (afterMediaItem.getWidth() * height) / afterMediaItem.getHeight(),
   2414                                 height, afterMediaItem.getTimelineDuration());
   2415                     } else {
   2416                         thumbnails[0] = null;
   2417                     }
   2418 
   2419                     final MediaItem beforeMediaItem = transition.getBeforeMediaItem();
   2420                     if (beforeMediaItem != null) {
   2421                         thumbnails[1] = beforeMediaItem.getThumbnail(
   2422                                 (beforeMediaItem.getWidth() * height) / beforeMediaItem.getHeight(),
   2423                                 height, 0);
   2424                     } else {
   2425                         thumbnails[1] = null;
   2426                     }
   2427 
   2428                     completeRequest(intent, videoEditor, null, thumbnails, null, true);
   2429                     break;
   2430                 }
   2431 
   2432                 case OP_EFFECT_ADD_COLOR: {
   2433                     logd("OP_EFFECT_ADD_COLOR: "
   2434                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2435 
   2436                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2437                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2438                     if (mediaItem == null) {
   2439                         throw new IllegalArgumentException("MediaItem not found: " +
   2440                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2441                     }
   2442 
   2443                     // Remove any existing effect
   2444                     final List<Effect> effects = mediaItem.getAllEffects();
   2445                     for (Effect effect : effects) {
   2446                         mediaItem.removeEffect(effect.getId());
   2447                     }
   2448 
   2449                     final Effect effect = new EffectColor(mediaItem,
   2450                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   2451                             intent.getLongExtra(PARAM_START_TIME, -1),
   2452                             intent.getLongExtra(PARAM_DURATION, 0),
   2453                             intent.getIntExtra(PARAM_EFFECT_TYPE, -1),
   2454                             intent.getIntExtra(PARAM_EFFECT_PARAM, -1));
   2455                     mediaItem.addEffect(effect);
   2456 
   2457                     completeRequest(intent, videoEditor, null, new MovieEffect(effect), null,
   2458                             false);
   2459                     generatePreview(videoEditor, true);
   2460                     completeRequest(intent);
   2461                     break;
   2462                 }
   2463 
   2464                 case OP_EFFECT_ADD_IMAGE_KEN_BURNS: {
   2465                     logd("OP_EFFECT_ADD_IMAGE_KEN_BURNS: "
   2466                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2467 
   2468                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2469                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2470                     if (mediaItem == null) {
   2471                         throw new IllegalArgumentException("MediaItem not found: " +
   2472                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2473                     }
   2474 
   2475                     // Remove any existing effect
   2476                     final List<Effect> effects = mediaItem.getAllEffects();
   2477                     for (Effect effect : effects) {
   2478                         mediaItem.removeEffect(effect.getId());
   2479                     }
   2480 
   2481                     final Effect effect = new EffectKenBurns(mediaItem,
   2482                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   2483                             (Rect)intent.getParcelableExtra(PARAM_MEDIA_ITEM_START_RECT),
   2484                             (Rect)intent.getParcelableExtra(PARAM_MEDIA_ITEM_END_RECT),
   2485                             intent.getLongExtra(PARAM_START_TIME, 0),
   2486                             intent.getLongExtra(PARAM_DURATION, 0));
   2487                     mediaItem.addEffect(effect);
   2488 
   2489                     completeRequest(intent, videoEditor, null, new MovieEffect(effect), null,
   2490                             false);
   2491                     generatePreview(videoEditor, true);
   2492                     completeRequest(intent);
   2493                     break;
   2494                 }
   2495 
   2496                 case OP_EFFECT_REMOVE: {
   2497                     logd("OP_EFFECT_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2498 
   2499                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2500                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2501                     if (mediaItem == null) {
   2502                         throw new IllegalArgumentException("MediaItem not found: " +
   2503                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2504                     }
   2505 
   2506                     mediaItem.removeEffect(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2507 
   2508                     completeRequest(intent, videoEditor, null, null, null, false);
   2509                     generatePreview(videoEditor, true);
   2510                     completeRequest(intent);
   2511                     break;
   2512                 }
   2513 
   2514                 case OP_OVERLAY_ADD: {
   2515                     logd("OP_OVERLAY_ADD: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2516 
   2517                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2518                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2519                     if (mediaItem == null) {
   2520                         throw new IllegalArgumentException("MediaItem not found: " +
   2521                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2522                     }
   2523 
   2524                     // Remove any existing overlays
   2525                     final List<Overlay> overlays = mediaItem.getAllOverlays();
   2526                     for (Overlay overlay : overlays) {
   2527                         mediaItem.removeOverlay(overlay.getId());
   2528                     }
   2529 
   2530                     final int scaledWidth, scaledHeight;
   2531                     if (mediaItem instanceof MediaVideoItem) {
   2532                         scaledWidth = ((MediaVideoItem)mediaItem).getWidth();
   2533                         scaledHeight = ((MediaVideoItem)mediaItem).getHeight();
   2534                     } else {
   2535                         scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth();
   2536                         scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight();
   2537                     }
   2538 
   2539                     final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES);
   2540 
   2541                     final int overlayType = MovieOverlay.getType(userAttributes);
   2542                     final String title = MovieOverlay.getTitle(userAttributes);
   2543                     final String subTitle = MovieOverlay.getSubtitle(userAttributes);
   2544 
   2545                     final OverlayFrame overlay = new OverlayFrame(mediaItem,
   2546                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   2547                             ImageUtils.buildOverlayBitmap(getApplicationContext(), null,
   2548                                     overlayType, title, subTitle, scaledWidth, scaledHeight),
   2549                             intent.getLongExtra(PARAM_START_TIME, -1),
   2550                             intent.getLongExtra(PARAM_DURATION, 0));
   2551 
   2552                     // Set the user attributes
   2553                     for (String name : userAttributes.keySet()) {
   2554                         if (MovieOverlay.getAttributeType(name).equals(Integer.class)) {
   2555                             overlay.setUserAttribute(name,
   2556                                     Integer.toString(userAttributes.getInt(name)));
   2557                         } else { // Strings
   2558                             overlay.setUserAttribute(name, userAttributes.getString(name));
   2559                         }
   2560                     }
   2561                     mediaItem.addOverlay(overlay);
   2562 
   2563                     completeRequest(intent, videoEditor, null, new MovieOverlay(overlay), null,
   2564                             false);
   2565                     generatePreview(videoEditor, true);
   2566                     completeRequest(intent);
   2567                     break;
   2568                 }
   2569 
   2570                 case OP_OVERLAY_REMOVE: {
   2571                     logd("OP_OVERLAY_REMOVE: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2572 
   2573                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2574                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2575                     if (mediaItem == null) {
   2576                         throw new IllegalArgumentException("MediaItem not found: " +
   2577                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2578                     }
   2579 
   2580                     mediaItem.removeOverlay(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2581 
   2582                     completeRequest(intent, videoEditor, null, null, null, false);
   2583                     generatePreview(videoEditor, true);
   2584                     completeRequest(intent);
   2585                     break;
   2586                 }
   2587 
   2588                 case OP_OVERLAY_SET_START_TIME: {
   2589                     logd("OP_OVERLAY_SET_START_TIME: "
   2590                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2591 
   2592                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2593                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2594                     if (mediaItem == null) {
   2595                         throw new IllegalArgumentException("MediaItem not found: " +
   2596                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2597                     }
   2598 
   2599                     final Overlay overlay = mediaItem.getOverlay(
   2600                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2601                     if (overlay == null) {
   2602                         throw new IllegalArgumentException("Overlay not found: " +
   2603                                 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2604                     }
   2605 
   2606                     overlay.setStartTime(intent.getLongExtra(PARAM_START_TIME, 0));
   2607 
   2608                     completeRequest(intent, videoEditor, null, null, null, false);
   2609                     generatePreview(videoEditor, true);
   2610                     completeRequest(intent);
   2611                     break;
   2612                 }
   2613 
   2614                 case OP_OVERLAY_SET_DURATION: {
   2615                     logd("OP_OVERLAY_SET_DURATION: "
   2616                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2617 
   2618                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2619                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2620                     if (mediaItem == null) {
   2621                         throw new IllegalArgumentException("MediaItem not found: " +
   2622                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2623                     }
   2624 
   2625                     final Overlay overlay = mediaItem.getOverlay(
   2626                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2627                     if (overlay == null) {
   2628                         throw new IllegalArgumentException("Overlay not found: " +
   2629                                 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2630                     }
   2631 
   2632                     overlay.setDuration(intent.getLongExtra(PARAM_DURATION, 0));
   2633 
   2634                     completeRequest(intent, videoEditor, null, null, null, false);
   2635                     generatePreview(videoEditor, true);
   2636                     completeRequest(intent);
   2637                     break;
   2638                 }
   2639 
   2640                 case OP_OVERLAY_SET_ATTRIBUTES: {
   2641                     logd("OP_OVERLAY_SET_ATTRIBUTES: "
   2642                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2643 
   2644                     final MediaItem mediaItem = videoEditor.getMediaItem(
   2645                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2646                     if (mediaItem == null) {
   2647                         throw new IllegalArgumentException("MediaItem not found: " +
   2648                                 intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID));
   2649                     }
   2650 
   2651                     final Overlay overlay = mediaItem.getOverlay(
   2652                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2653                     if (overlay == null) {
   2654                         throw new IllegalArgumentException("Overlay not found: " +
   2655                                 intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2656                     }
   2657 
   2658                     final int scaledWidth, scaledHeight;
   2659                     if (mediaItem instanceof MediaVideoItem) {
   2660                         scaledWidth = ((MediaVideoItem)mediaItem).getWidth();
   2661                         scaledHeight = ((MediaVideoItem)mediaItem).getHeight();
   2662                     } else {
   2663                         scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth();
   2664                         scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight();
   2665                     }
   2666 
   2667                     final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES);
   2668                     final int overlayType = MovieOverlay.getType(userAttributes);
   2669                     final String title = MovieOverlay.getTitle(userAttributes);
   2670                     final String subTitle = MovieOverlay.getSubtitle(userAttributes);
   2671 
   2672                     ((OverlayFrame)overlay).setBitmap(
   2673                             ImageUtils.buildOverlayBitmap(getApplicationContext(), null,
   2674                                     overlayType, title, subTitle, scaledWidth, scaledHeight));
   2675 
   2676                     for (String name : userAttributes.keySet()) {
   2677                         if (MovieOverlay.getAttributeType(name).equals(Integer.class)) {
   2678                             overlay.setUserAttribute(name,
   2679                                     Integer.toString(userAttributes.getInt(name)));
   2680                         } else { // Strings
   2681                             overlay.setUserAttribute(name, userAttributes.getString(name));
   2682                         }
   2683                     }
   2684 
   2685                     completeRequest(intent, videoEditor, null, null, null, false);
   2686                     generatePreview(videoEditor, true);
   2687                     completeRequest(intent);
   2688                     break;
   2689                 }
   2690 
   2691                 case OP_AUDIO_TRACK_ADD: {
   2692                     logd("OP_AUDIO_TRACK_ADD: " + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2693 
   2694                     final Uri data = intent.getParcelableExtra(PARAM_FILENAME);
   2695                     String filename = null;
   2696                     // Get the filename
   2697                     Cursor cursor = null;
   2698                     try {
   2699                         cursor = getContentResolver().query(data,
   2700                                 new String[] {Audio.Media.DATA}, null, null, null);
   2701                         if (cursor.moveToFirst()) {
   2702                             filename = cursor.getString(0);
   2703                         }
   2704                     } finally {
   2705                         if (cursor != null) {
   2706                             cursor.close();
   2707                         }
   2708                     }
   2709 
   2710                     if (filename == null) {
   2711                         throw new IllegalArgumentException("Media file not found: " + data);
   2712                     }
   2713 
   2714                     final AudioTrack audioTrack = new AudioTrack(videoEditor,
   2715                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), filename);
   2716                     audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME);
   2717                     audioTrack.setVolume(DEFAULT_AUDIO_TRACK_VOLUME);
   2718                     if (intent.getBooleanExtra(PARAM_LOOP, false)) {
   2719                         audioTrack.enableLoop();
   2720                     } else {
   2721                         audioTrack.disableLoop();
   2722                     }
   2723 
   2724                     videoEditor.addAudioTrack(audioTrack);
   2725 
   2726                     completeRequest(intent, videoEditor, null, new MovieAudioTrack(audioTrack),
   2727                             null, false);
   2728                     // This is needed to decode the audio file into a PCM file
   2729                     generatePreview(videoEditor, false);
   2730                     completeRequest(intent);
   2731                     break;
   2732                 }
   2733 
   2734                 case OP_AUDIO_TRACK_REMOVE: {
   2735                     logd("OP_AUDIO_TRACK_REMOVE: "
   2736                             + intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2737 
   2738                     videoEditor.removeAudioTrack(intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID));
   2739 
   2740                     completeRequest(intent, videoEditor, null, null, null, false);
   2741                     generatePreview(videoEditor, false);
   2742                     completeRequest(intent);
   2743                     break;
   2744                 }
   2745 
   2746                 case OP_AUDIO_TRACK_SET_BOUNDARIES: {
   2747                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2748                     logd("OP_AUDIO_TRACK_SET_BOUNDARIES: " + audioTrackId);
   2749 
   2750                     final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId);
   2751                     if (audioTrack == null) {
   2752                         throw new IllegalArgumentException("AudioTrack not found: " +
   2753                                 audioTrackId);
   2754                     }
   2755 
   2756                     audioTrack.setExtractBoundaries(intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0),
   2757                             intent.getLongExtra(PARAM_END_BOUNDARY, 0));
   2758 
   2759                     completeRequest(intent, videoEditor, null, null, null, false);
   2760                     generatePreview(videoEditor, false);
   2761                     completeRequest(intent);
   2762                     break;
   2763                 }
   2764 
   2765                 case OP_AUDIO_TRACK_SET_LOOP: {
   2766                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2767                     logd("OP_AUDIO_TRACK_SET_LOOP: " + audioTrackId);
   2768 
   2769                     final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId);
   2770                     if (audioTrack == null) {
   2771                         throw new IllegalArgumentException("AudioTrack not found: " +
   2772                                 audioTrackId);
   2773                     }
   2774 
   2775                     if (intent.getBooleanExtra(PARAM_LOOP, false)) {
   2776                         audioTrack.enableLoop();
   2777                     } else {
   2778                         audioTrack.disableLoop();
   2779                     }
   2780 
   2781                     completeRequest(intent, videoEditor, null, null, null, false);
   2782                     generatePreview(videoEditor, false);
   2783                     completeRequest(intent);
   2784                     break;
   2785                 }
   2786 
   2787                 case OP_AUDIO_TRACK_SET_DUCK: {
   2788                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2789                     logd("OP_AUDIO_TRACK_SET_DUCK: " + audioTrackId);
   2790 
   2791                     final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId);
   2792                     if (audioTrack == null) {
   2793                         throw new IllegalArgumentException("AudioTrack not found: " +
   2794                                 audioTrackId);
   2795                     }
   2796 
   2797                     if (intent.getBooleanExtra(PARAM_DUCK, false)) {
   2798                         audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME);
   2799                     } else {
   2800                         audioTrack.disableDucking();
   2801                     }
   2802 
   2803                     completeRequest(intent, videoEditor, null, null, null, false);
   2804                     generatePreview(videoEditor, false);
   2805                     completeRequest(intent);
   2806                     break;
   2807                 }
   2808 
   2809                 case OP_AUDIO_TRACK_SET_VOLUME: {
   2810                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2811                     logd("OP_AUDIO_TRACK_SET_VOLUME: " + audioTrackId);
   2812 
   2813                     final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId);
   2814                     if (audioTrack == null) {
   2815                         throw new IllegalArgumentException("AudioTrack not found: " +
   2816                                 audioTrackId);
   2817                     }
   2818 
   2819                     audioTrack.setVolume(intent.getIntExtra(PARAM_VOLUME, 0));
   2820 
   2821                     completeRequest(intent, videoEditor, null, null, null, false);
   2822                     generatePreview(videoEditor, false);
   2823                     completeRequest(intent);
   2824                     break;
   2825                 }
   2826 
   2827                 case OP_AUDIO_TRACK_SET_MUTE: {
   2828                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2829                     logd("OP_AUDIO_TRACK_SET_MUTE: " + audioTrackId);
   2830 
   2831                     final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId);
   2832                     if (audioTrack == null) {
   2833                         throw new IllegalArgumentException("AudioTrack not found: " +
   2834                                 audioTrackId);
   2835                     }
   2836 
   2837                     audioTrack.setMute(intent.getBooleanExtra(PARAM_MUTE, false));
   2838 
   2839                     completeRequest(intent, videoEditor, null, null, null, false);
   2840                     generatePreview(videoEditor, false);
   2841                     completeRequest(intent);
   2842                     break;
   2843                 }
   2844 
   2845                 case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: {
   2846                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   2847                     logd("OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: " + audioTrackId);
   2848 
   2849                     final AudioTrack audioTrack = videoEditor.getAudioTrack(audioTrackId);
   2850                     if (audioTrack == null) {
   2851                         throw new IllegalArgumentException("AudioTrack not found: " +
   2852                                 audioTrackId);
   2853                     }
   2854 
   2855                     final WaveformData waveformData = audioTrack.getWaveformData();
   2856                     if (waveformData == null) {
   2857                         extractAudioTrackAudioWaveform(intent, videoEditor, audioTrack);
   2858                         completeRequest(intent, videoEditor, null, audioTrack.getWaveformData(),
   2859                                 null, true);
   2860                     } else {
   2861                         completeRequest(intent, videoEditor, null, waveformData, null, true);
   2862                     }
   2863                     break;
   2864                 }
   2865 
   2866                 default: {
   2867                     throw new IllegalArgumentException("Unhandled operation: " + op);
   2868                 }
   2869             }
   2870         } catch (Exception ex) {
   2871             ex.printStackTrace();
   2872             completeRequest(intent, videoEditor, ex, null, null, true);
   2873         }
   2874     }
   2875 
   2876     /**
   2877      * Complete the request
   2878      *
   2879      * @param intent The intent
   2880      * @param videoEditor The video editor
   2881      * @param exception The exception
   2882      * @param result The result object
   2883      * @param extraResult The extra result object
   2884      * @param finalize true if the request should be finalized
   2885      */
   2886     private void completeRequest(final Intent intent, final VideoEditor videoEditor,
   2887             final Exception exception, final Object result, final Object extraResult,
   2888             final boolean finalize) {
   2889         mHandler.post(new Runnable() {
   2890             @Override
   2891             public void run() {
   2892                 onIntentProcessed(intent, videoEditor, result, extraResult, exception, finalize);
   2893             }
   2894         });
   2895     }
   2896 
   2897     /**
   2898      * Complete the request
   2899      *
   2900      * @param intent The intent
   2901      */
   2902     private void completeRequest(final Intent intent) {
   2903         mHandler.post (new Runnable() {
   2904             @Override
   2905             public void run() {
   2906                 finalizeRequest(intent);
   2907                 mIntentPool.put(intent);
   2908             }
   2909         });
   2910     }
   2911 
   2912     /**
   2913      * Callback called after the specified intent is processed.
   2914      *
   2915      * @param intent The intent
   2916      * @param videoEditor The VideoEditor on which the operation was performed
   2917      * @param result The result object
   2918      * @param extraResult The extra result object
   2919      * @param ex The exception
   2920      * @param finalize true if the request should be finalized
   2921      */
   2922     @SuppressWarnings("unchecked")
   2923     public void onIntentProcessed(final Intent intent, VideoEditor videoEditor,
   2924             Object result, Object extraResult, Exception ex, boolean finalize) {
   2925 
   2926         final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH);
   2927         final int op = intent.getIntExtra(PARAM_OP, -1);
   2928         switch (op) {
   2929             case OP_VIDEO_EDITOR_LOAD_PROJECTS: {
   2930                 if (finalize) {
   2931                     finalizeRequest(intent);
   2932                 }
   2933 
   2934                 final List<VideoEditorProject> projects = (List<VideoEditorProject>)result;
   2935                 for (ApiServiceListener listener : mListeners) {
   2936                     listener.onProjectsLoaded(projects, ex);
   2937                 }
   2938 
   2939                 break;
   2940             }
   2941 
   2942             case OP_VIDEO_EDITOR_CREATE: {
   2943                 if (finalize) {
   2944                     finalizeRequest(intent);
   2945                 }
   2946 
   2947                 // Release the old project
   2948                 if (mVideoProject != null) {
   2949                     mVideoProject.release();
   2950                     mVideoProject = null;
   2951                 }
   2952 
   2953                 if (ex != null) {
   2954                     FileUtils.deleteDir(new File(projectPath));
   2955                 } else {
   2956                     mVideoProject = (VideoEditorProject)result;
   2957                 }
   2958 
   2959                 for (ApiServiceListener listener : mListeners) {
   2960                     listener.onVideoEditorCreated(projectPath, mVideoProject,
   2961                             videoEditor != null ? videoEditor.getAllMediaItems() : null,
   2962                             videoEditor != null ? videoEditor.getAllAudioTracks() : null, ex);
   2963                 }
   2964 
   2965                 break;
   2966             }
   2967 
   2968             case OP_VIDEO_EDITOR_LOAD: {
   2969                 if (finalize) {
   2970                     finalizeRequest(intent);
   2971                 }
   2972 
   2973                 if (result != null) { // A new project was created
   2974                     if (mVideoProject != null) {
   2975                         mVideoProject.release();
   2976                         mVideoProject = null;
   2977                     }
   2978                     mVideoProject = (VideoEditorProject)result;
   2979                 }
   2980 
   2981                 for (ApiServiceListener listener : mListeners) {
   2982                     listener.onVideoEditorLoaded(projectPath, mVideoProject,
   2983                             ex == null ? videoEditor.getAllMediaItems() : null,
   2984                             ex == null ? videoEditor.getAllAudioTracks() : null, ex);
   2985                 }
   2986 
   2987                 break;
   2988             }
   2989 
   2990             case OP_VIDEO_EDITOR_SET_ASPECT_RATIO: {
   2991                 if (finalize) {
   2992                     finalizeRequest(intent);
   2993                 }
   2994 
   2995                 final int aspectRatio = intent.getIntExtra(PARAM_ASPECT_RATIO,
   2996                         MediaProperties.ASPECT_RATIO_UNDEFINED);
   2997                 if (ex == null) {
   2998                     final VideoEditorProject videoProject = getProject(projectPath);
   2999                     if (videoProject != null) {
   3000                         videoProject.setAspectRatio(aspectRatio);
   3001                     }
   3002                 }
   3003 
   3004                 for (ApiServiceListener listener : mListeners) {
   3005                     listener.onVideoEditorAspectRatioSet(projectPath, aspectRatio, ex);
   3006                 }
   3007 
   3008                 break;
   3009             }
   3010 
   3011             case OP_VIDEO_EDITOR_APPLY_THEME: {
   3012                 if (finalize) {
   3013                     finalizeRequest(intent);
   3014                 }
   3015 
   3016                 final String theme = intent.getStringExtra(PARAM_THEME);
   3017                 if (ex == null) {
   3018                     final VideoEditorProject videoProject = getProject(projectPath);
   3019                     if (videoProject != null) {
   3020                         videoProject.setTheme(theme);
   3021                         videoProject.setMediaItems((List<MovieMediaItem>)result);
   3022                         videoProject.setAudioTracks((List<MovieAudioTrack>)extraResult);
   3023                     }
   3024                 }
   3025 
   3026                 for (ApiServiceListener listener : mListeners) {
   3027                     listener.onVideoEditorThemeApplied(projectPath, theme, ex);
   3028                 }
   3029 
   3030                 break;
   3031             }
   3032 
   3033             case OP_VIDEO_EDITOR_GENERATE_PREVIEW_PROGRESS: {
   3034                 final String className = intent.getStringExtra(PARAM_ATTRIBUTES);
   3035                 final String itemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3036                 final int action = intent.getIntExtra(PARAM_ACTION,
   3037                         VideoEditor.MediaProcessingProgressListener.ACTION_DECODE);
   3038                 final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0);
   3039 
   3040                 for (ApiServiceListener listener : mListeners) {
   3041                     listener.onVideoEditorGeneratePreviewProgress(projectPath, className, itemId,
   3042                             action, progress);
   3043                 }
   3044 
   3045                 break;
   3046             }
   3047 
   3048             case OP_VIDEO_EDITOR_EXPORT: {
   3049                 // The finalizeRequest() call and listener callbacks are done in
   3050                 // OP_VIDEO_EDITOR_EXPORT_STATUS intent handling (where we are
   3051                 // called originalIntent).
   3052                 break;
   3053             }
   3054 
   3055             case OP_VIDEO_EDITOR_CANCEL_EXPORT: {
   3056                 if (finalize) {
   3057                     finalizeRequest(intent);
   3058                 }
   3059 
   3060                 for (ApiServiceListener listener : mListeners) {
   3061                     listener.onVideoEditorExportCanceled(projectPath,
   3062                             intent.getStringExtra(PARAM_FILENAME));
   3063                 }
   3064                 break;
   3065             }
   3066 
   3067             case OP_VIDEO_EDITOR_EXPORT_STATUS: {
   3068                 // This operation is for the service internal use only
   3069                 if (finalize) {
   3070                     finalizeRequest(intent);
   3071                 }
   3072 
   3073                 final String filename = intent.getStringExtra(PARAM_FILENAME);
   3074                 if (intent.hasExtra(PARAM_EXCEPTION)) { // Complete
   3075                     final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT);
   3076                     finalizeRequest(originalIntent);
   3077                     mIntentPool.put(originalIntent);
   3078 
   3079                     final Exception exception =
   3080                         (Exception)intent.getSerializableExtra(PARAM_EXCEPTION);
   3081                     final VideoEditorProject videoProject = getProject(projectPath);
   3082                     final boolean cancelled = intent.getBooleanExtra(PARAM_CANCELLED, false);
   3083                     if (!cancelled && videoProject != null && exception == null) {
   3084                         final Uri uri = (Uri)intent.getParcelableExtra(PARAM_MOVIE_URI);
   3085                         videoProject.addExportedMovieUri(uri);
   3086                     }
   3087 
   3088                     for (ApiServiceListener listener : mListeners) {
   3089                         listener.onVideoEditorExportComplete(
   3090                                 projectPath, filename, exception, cancelled);
   3091                     }
   3092                 } else { // Progress
   3093                     for (ApiServiceListener listener : mListeners) {
   3094                         listener.onVideoEditorExportProgress(projectPath, filename,
   3095                                 intent.getIntExtra(PARAM_PROGRESS_VALUE, -1));
   3096                     }
   3097 
   3098                     // The original request is still pending
   3099                 }
   3100                 break;
   3101             }
   3102 
   3103             case OP_VIDEO_EDITOR_SAVE: {
   3104                 if (finalize) {
   3105                     finalizeRequest(intent);
   3106                 }
   3107 
   3108                 for (ApiServiceListener listener : mListeners) {
   3109                     listener.onVideoEditorSaved(projectPath, ex);
   3110                 }
   3111                 break;
   3112             }
   3113 
   3114             case OP_VIDEO_EDITOR_RELEASE: {
   3115                 if (finalize) {
   3116                     finalizeRequest(intent);
   3117                 }
   3118 
   3119                 final VideoEditorProject videoProject = getProject(projectPath);
   3120                 if (videoProject != null) {
   3121                     videoProject.release();
   3122                     if (mVideoProject == videoProject) {
   3123                         mVideoProject = null;
   3124                     }
   3125                 }
   3126 
   3127                 for (ApiServiceListener listener : mListeners) {
   3128                     listener.onVideoEditorReleased(projectPath, ex);
   3129                 }
   3130 
   3131                 break;
   3132             }
   3133 
   3134             case OP_VIDEO_EDITOR_DELETE: {
   3135                 if (finalize) {
   3136                     finalizeRequest(intent);
   3137                 }
   3138 
   3139                 final VideoEditorProject videoProject = getProject(projectPath);
   3140                 if (videoProject != null) {
   3141                     videoProject.release();
   3142                     if (mVideoProject == videoProject) {
   3143                         mVideoProject = null;
   3144                     }
   3145                 }
   3146 
   3147                 for (ApiServiceListener listener : mListeners) {
   3148                     listener.onVideoEditorDeleted(projectPath, ex);
   3149                 }
   3150 
   3151                 break;
   3152             }
   3153 
   3154             case OP_MEDIA_ITEM_ADD_VIDEO_URI: {
   3155                 if (finalize) {
   3156                     finalizeRequest(intent);
   3157                 }
   3158 
   3159                 final String afterMediaItemId =
   3160                     intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3161 
   3162                 final MovieMediaItem movieMediaItem = (MovieMediaItem)result;
   3163                 final VideoEditorProject videoProject = getProject(projectPath);
   3164                 if (videoProject != null) {
   3165                     if (ex == null && extraResult != null) {
   3166                         // The aspect ratio has changed
   3167                         videoProject.setAspectRatio((Integer)extraResult);
   3168                     }
   3169 
   3170                     if (ex == null) {
   3171                         videoProject.insertMediaItem(movieMediaItem, afterMediaItemId);
   3172                     }
   3173                 }
   3174 
   3175                 for (ApiServiceListener listener : mListeners) {
   3176                     listener.onMediaItemAdded(projectPath,
   3177                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), movieMediaItem,
   3178                             afterMediaItemId, MediaVideoItem.class, (Integer)extraResult, ex);
   3179                 }
   3180 
   3181                 break;
   3182             }
   3183 
   3184             case OP_MEDIA_ITEM_ADD_IMAGE_URI: {
   3185                 if (finalize) {
   3186                     finalizeRequest(intent);
   3187                 }
   3188 
   3189                 final String afterMediaItemId =
   3190                     intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3191 
   3192                 final MovieMediaItem movieMediaItem = (MovieMediaItem)result;
   3193                 final VideoEditorProject videoProject = getProject(projectPath);
   3194                 if (videoProject != null) {
   3195                     if (ex == null && extraResult != null) {
   3196                         // The aspect ratio has changed
   3197                         videoProject.setAspectRatio((Integer)extraResult);
   3198                     }
   3199 
   3200                     if (ex == null) {
   3201                         videoProject.insertMediaItem(movieMediaItem, afterMediaItemId);
   3202                     }
   3203                 }
   3204 
   3205                 for (ApiServiceListener listener : mListeners) {
   3206                     listener.onMediaItemAdded(projectPath,
   3207                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID), movieMediaItem,
   3208                             afterMediaItemId, MediaImageItem.class, (Integer)extraResult, ex);
   3209                 }
   3210 
   3211                 break;
   3212             }
   3213 
   3214             case OP_MEDIA_ITEM_LOAD: {
   3215                 // Note that this message is handled only if the download
   3216                 // cannot start.
   3217                 final Uri data = (Uri)intent.getParcelableExtra(PARAM_FILENAME);
   3218                 final String mimeType = intent.getStringExtra(PARAM_ATTRIBUTES);
   3219                 if (finalize) {
   3220                     finalizeRequest(intent);
   3221                 }
   3222 
   3223                 for (ApiServiceListener listener : mListeners) {
   3224                     listener.onMediaLoaded(projectPath, data, mimeType, null, ex);
   3225                 }
   3226                 break;
   3227             }
   3228 
   3229             case OP_MEDIA_ITEM_LOAD_STATUS: {
   3230                 if (finalize) {
   3231                     finalizeRequest(intent);
   3232                 }
   3233 
   3234                 final Intent originalIntent = (Intent)intent.getParcelableExtra(PARAM_INTENT);
   3235                 final Uri data = (Uri)originalIntent.getParcelableExtra(PARAM_FILENAME);
   3236                 final String mimeType = originalIntent.getStringExtra(PARAM_ATTRIBUTES);
   3237 
   3238                 finalizeRequest(originalIntent);
   3239                 mIntentPool.put(originalIntent);
   3240 
   3241                 final String filename = intent.getStringExtra(PARAM_FILENAME);
   3242 
   3243                 if (ex == null && filename != null) {
   3244                     final VideoEditorProject videoProject = getProject(projectPath);
   3245                     videoProject.addDownload(data.toString(), mimeType, filename);
   3246                 }
   3247 
   3248                 for (ApiServiceListener listener : mListeners) {
   3249                     listener.onMediaLoaded(projectPath, data, mimeType, filename, ex);
   3250                 }
   3251                 break;
   3252             }
   3253 
   3254             case OP_MEDIA_ITEM_MOVE: {
   3255                 if (finalize) {
   3256                     finalizeRequest(intent);
   3257                 }
   3258 
   3259                 if (ex == null) {
   3260                     final VideoEditorProject videoProject = getProject(projectPath);
   3261                     if (videoProject != null) {
   3262                         videoProject.setMediaItems((List<MovieMediaItem>)result);
   3263                     }
   3264                 }
   3265 
   3266                 for (ApiServiceListener listener : mListeners) {
   3267                     listener.onMediaItemMoved(projectPath,
   3268                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   3269                             intent.getStringExtra(PARAM_RELATIVE_STORYBOARD_ITEM_ID), ex);
   3270                 }
   3271 
   3272                 break;
   3273             }
   3274 
   3275             case OP_MEDIA_ITEM_REMOVE: {
   3276                 if (finalize) {
   3277                     finalizeRequest(intent);
   3278                 }
   3279 
   3280                 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3281                 final MovieTransition transition = (MovieTransition)result;
   3282                 if (ex == null) {
   3283                     final VideoEditorProject videoProject = getProject(projectPath);
   3284                     if (videoProject != null) {
   3285                         videoProject.removeMediaItem(mediaItemId, transition);
   3286                     }
   3287                 }
   3288 
   3289                 for (ApiServiceListener listener : mListeners) {
   3290                     listener.onMediaItemRemoved(projectPath, mediaItemId, transition, ex);
   3291                 }
   3292 
   3293                 break;
   3294             }
   3295 
   3296             case OP_MEDIA_ITEM_SET_RENDERING_MODE: {
   3297                 if (finalize) {
   3298                     finalizeRequest(intent);
   3299                 }
   3300 
   3301                 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3302                 final int renderingMode = intent.getIntExtra(PARAM_MEDIA_ITEM_RENDERING_MODE,
   3303                         MediaItem.RENDERING_MODE_BLACK_BORDER);
   3304 
   3305                 final VideoEditorProject videoProject = getProject(projectPath);
   3306                 if (videoProject != null) {
   3307                     final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId);
   3308                     if (mediaItem != null) {
   3309                         videoProject.setClean(false);
   3310                         if (ex == null) {
   3311                             mediaItem.setRenderingMode(renderingMode);
   3312                         } else {
   3313                             mediaItem.setAppRenderingMode(mediaItem.getRenderingMode());
   3314                         }
   3315                     }
   3316                 }
   3317 
   3318                 for (ApiServiceListener listener : mListeners) {
   3319                     listener.onMediaItemRenderingModeSet(projectPath, mediaItemId, renderingMode,
   3320                             ex);
   3321                 }
   3322 
   3323                 break;
   3324             }
   3325 
   3326             case OP_MEDIA_ITEM_SET_DURATION: {
   3327                 if (finalize) {
   3328                     finalizeRequest(intent);
   3329                 }
   3330 
   3331                 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3332 
   3333                 final VideoEditorProject videoProject = getProject(projectPath);
   3334                 if (videoProject != null) {
   3335                     if (ex == null) {
   3336                         videoProject.updateMediaItem((MovieMediaItem)result);
   3337                     } else {
   3338                         final MovieMediaItem oldMediaItem = videoProject.getMediaItem(mediaItemId);
   3339                         if (oldMediaItem != null) {
   3340                             videoProject.setClean(false);
   3341                             oldMediaItem.setAppExtractBoundaries(0, oldMediaItem.getDuration());
   3342                         }
   3343                     }
   3344                 }
   3345 
   3346                 for (ApiServiceListener listener : mListeners) {
   3347                     listener.onMediaItemDurationSet(projectPath, mediaItemId,
   3348                             intent.getLongExtra(PARAM_DURATION, 0), ex);
   3349                 }
   3350 
   3351                 break;
   3352             }
   3353 
   3354             case OP_MEDIA_ITEM_SET_BOUNDARIES: {
   3355                 if (finalize) {
   3356                     finalizeRequest(intent);
   3357                 }
   3358 
   3359                 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3360 
   3361                 final VideoEditorProject videoProject = getProject(projectPath);
   3362                 if (videoProject != null) {
   3363                     if (ex == null) {
   3364                         final MovieMediaItem mediaItem = (MovieMediaItem)result;
   3365                         videoProject.updateMediaItem(mediaItem);
   3366                     } else {
   3367                         final MovieMediaItem oldMediaItem = videoProject.getMediaItem(mediaItemId);
   3368                         if (oldMediaItem != null) {
   3369                             videoProject.setClean(false);
   3370                             oldMediaItem.setAppExtractBoundaries(
   3371                                     oldMediaItem.getBoundaryBeginTime(),
   3372                                     oldMediaItem.getBoundaryEndTime());
   3373                         }
   3374                     }
   3375                 }
   3376 
   3377                 for (ApiServiceListener listener : mListeners) {
   3378                     listener.onMediaItemBoundariesSet(projectPath, mediaItemId,
   3379                             intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0),
   3380                             intent.getLongExtra(PARAM_END_BOUNDARY, 0), ex);
   3381                 }
   3382 
   3383                 break;
   3384             }
   3385 
   3386             case OP_MEDIA_ITEM_GET_THUMBNAILS: {
   3387                 if (finalize) {
   3388                     finalizeRequest(intent);
   3389                     break;
   3390                 }
   3391 
   3392                 final Bitmap bitmap = (Bitmap)result;
   3393                 final int index = (Integer)extraResult;
   3394                 boolean used = false;
   3395                 for (ApiServiceListener listener : mListeners) {
   3396                     used |= listener.onMediaItemThumbnail(projectPath,
   3397                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   3398                             bitmap, index, intent.getIntExtra(PARAM_TOKEN, 0),
   3399                             ex);
   3400                 }
   3401 
   3402                 if (used == false) {
   3403                     if (bitmap != null) {
   3404                         bitmap.recycle();
   3405                     }
   3406                 }
   3407 
   3408                 break;
   3409             }
   3410 
   3411             case OP_MEDIA_ITEM_SET_VOLUME: {
   3412                 if (finalize) {
   3413                     finalizeRequest(intent);
   3414                 }
   3415 
   3416                 final VideoEditorProject videoProject = getProject(projectPath);
   3417                 if (videoProject != null) {
   3418                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3419                     final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId);
   3420                     if (mediaItem != null) {
   3421                         videoProject.setClean(false);
   3422                         if (ex == null) {
   3423                             mediaItem.setVolume(intent.getIntExtra(PARAM_VOLUME, 0));
   3424                         } else {
   3425                             mediaItem.setAppVolume(mediaItem.getVolume());
   3426                         }
   3427                     }
   3428                 }
   3429 
   3430                 break;
   3431             }
   3432 
   3433             case OP_MEDIA_ITEM_SET_MUTE: {
   3434                 if (finalize) {
   3435                     finalizeRequest(intent);
   3436                 }
   3437 
   3438                 final VideoEditorProject videoProject = getProject(projectPath);
   3439                 if (videoProject != null) {
   3440                     final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3441                     final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId);
   3442                     if (mediaItem != null) {
   3443                         videoProject.setClean(false);
   3444                         if (ex == null) {
   3445                             mediaItem.setMute(intent.getBooleanExtra(PARAM_MUTE, false));
   3446                         } else {
   3447                             mediaItem.setAppMute(mediaItem.isMuted());
   3448                         }
   3449                     }
   3450                 }
   3451 
   3452                 break;
   3453             }
   3454 
   3455             case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS: {
   3456                 if (finalize) {
   3457                     finalizeRequest(intent);
   3458                 }
   3459 
   3460                 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3461                 final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0);
   3462 
   3463                 for (ApiServiceListener listener : mListeners) {
   3464                     listener.onMediaItemExtractAudioWaveformProgress(projectPath, mediaItemId,
   3465                         progress);
   3466                 }
   3467 
   3468                 break;
   3469             }
   3470 
   3471             case OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM: {
   3472                 if (finalize) {
   3473                     finalizeRequest(intent);
   3474                 }
   3475 
   3476                 final String mediaItemId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3477 
   3478                 final VideoEditorProject videoProject = getProject(projectPath);
   3479                 if (ex == null && videoProject != null) {
   3480                     if (result != null) {
   3481                         final MovieMediaItem mediaItem = videoProject.getMediaItem(mediaItemId);
   3482                         if (mediaItem != null) {
   3483                             videoProject.setClean(false);
   3484                             mediaItem.setWaveformData((WaveformData)result);
   3485                         }
   3486                     }
   3487                 }
   3488 
   3489                 for (ApiServiceListener listener : mListeners) {
   3490                     listener.onMediaItemExtractAudioWaveformComplete(projectPath, mediaItemId, ex);
   3491                 }
   3492 
   3493                 break;
   3494             }
   3495 
   3496             case OP_TRANSITION_INSERT_ALPHA:
   3497             case OP_TRANSITION_INSERT_CROSSFADE:
   3498             case OP_TRANSITION_INSERT_FADE_BLACK:
   3499             case OP_TRANSITION_INSERT_SLIDING: {
   3500                 if (finalize) {
   3501                     finalizeRequest(intent);
   3502                 }
   3503 
   3504                 final String afterMediaItemId = intent.getStringExtra(
   3505                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3506 
   3507                 final MovieTransition movieTransition;
   3508                 final VideoEditorProject videoProject = getProject(projectPath);
   3509                 if (videoProject != null) {
   3510                     final Transition transition = (Transition)result;
   3511                     // This is null for start transitions
   3512                     if (ex == null) {
   3513                         movieTransition = new MovieTransition(transition);
   3514                         videoProject.addTransition(movieTransition, afterMediaItemId);
   3515                     } else {
   3516                         movieTransition = null;
   3517                     }
   3518                 } else {
   3519                     movieTransition = null;
   3520                 }
   3521 
   3522                 for (ApiServiceListener listener : mListeners) {
   3523                     listener.onTransitionInserted(projectPath, movieTransition,
   3524                             afterMediaItemId, ex);
   3525                 }
   3526 
   3527                 break;
   3528             }
   3529 
   3530             case OP_TRANSITION_REMOVE: {
   3531                 if (finalize) {
   3532                     finalizeRequest(intent);
   3533                 }
   3534 
   3535                 final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3536 
   3537                 final VideoEditorProject videoProject = getProject(projectPath);
   3538                 if (videoProject != null) {
   3539                     if (ex == null) {
   3540                         videoProject.removeTransition(transitionId);
   3541                     }
   3542                 }
   3543 
   3544                 for (ApiServiceListener listener : mListeners) {
   3545                     listener.onTransitionRemoved(projectPath, transitionId, ex);
   3546                 }
   3547 
   3548                 break;
   3549             }
   3550 
   3551             case OP_TRANSITION_SET_DURATION: {
   3552                 if (finalize) {
   3553                     finalizeRequest(intent);
   3554                 }
   3555 
   3556                 final String transitionId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3557                 final long durationMs = intent.getLongExtra(PARAM_DURATION, 0);
   3558 
   3559                 final VideoEditorProject videoProject = getProject(projectPath);
   3560                 if (videoProject != null) {
   3561                     final MovieTransition transition = videoProject.getTransition(transitionId);
   3562                     if (transition != null) {
   3563                         videoProject.setClean(false);
   3564                         if (ex == null) {
   3565                             transition.setDuration(durationMs);
   3566                         } else {
   3567                             transition.setAppDuration(transition.getDuration());
   3568                         }
   3569                     }
   3570                 }
   3571 
   3572                 for (ApiServiceListener listener : mListeners) {
   3573                     listener.onTransitionDurationSet(projectPath, transitionId, durationMs, ex);
   3574                 }
   3575 
   3576                 break;
   3577             }
   3578 
   3579             case OP_TRANSITION_GET_THUMBNAIL: {
   3580                 if (finalize) {
   3581                     finalizeRequest(intent);
   3582                 }
   3583 
   3584                 final Bitmap[] bitmaps = (Bitmap[])result;
   3585                 boolean used = false;
   3586                 for (ApiServiceListener listener : mListeners) {
   3587                     used |= listener.onTransitionThumbnails(projectPath,
   3588                             intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID),
   3589                             bitmaps, ex);
   3590                 }
   3591 
   3592                 if (used == false) {
   3593                     if (bitmaps != null) {
   3594                         for (int i = 0; i < bitmaps.length; i++) {
   3595                             if (bitmaps[i] != null) {
   3596                                 bitmaps[i].recycle();
   3597                             }
   3598                         }
   3599                     }
   3600                 }
   3601 
   3602                 break;
   3603             }
   3604 
   3605             case OP_OVERLAY_ADD: {
   3606                 if (finalize) {
   3607                     finalizeRequest(intent);
   3608                 }
   3609 
   3610                 final String mediaItemId = intent.getStringExtra(
   3611                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3612 
   3613                 final MovieOverlay movieOverlay = (MovieOverlay)result;
   3614                 final VideoEditorProject videoProject = getProject(projectPath);
   3615                 if (videoProject != null) {
   3616                     if (ex == null) {
   3617                         videoProject.addOverlay(mediaItemId, movieOverlay);
   3618                     }
   3619                 }
   3620 
   3621                 for (ApiServiceListener listener : mListeners) {
   3622                     listener.onOverlayAdded(projectPath, movieOverlay, mediaItemId, ex);
   3623                 }
   3624 
   3625                 break;
   3626             }
   3627 
   3628             case OP_OVERLAY_REMOVE: {
   3629                 if (finalize) {
   3630                     finalizeRequest(intent);
   3631                 }
   3632 
   3633                 final String mediaItemId = intent.getStringExtra(
   3634                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3635                 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3636 
   3637                 final VideoEditorProject videoProject = getProject(projectPath);
   3638                 if (videoProject != null) {
   3639                     if (ex == null) {
   3640                         videoProject.removeOverlay(mediaItemId, overlayId);
   3641                     }
   3642                 }
   3643 
   3644                 for (ApiServiceListener listener : mListeners) {
   3645                     listener.onOverlayRemoved(projectPath, overlayId, mediaItemId, ex);
   3646                 }
   3647 
   3648                 break;
   3649             }
   3650 
   3651             case OP_OVERLAY_SET_START_TIME: {
   3652                 if (finalize) {
   3653                     finalizeRequest(intent);
   3654                 }
   3655 
   3656                 final String mediaItemId = intent.getStringExtra(
   3657                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3658                 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3659                 final long startTimeMs = intent.getLongExtra(PARAM_START_TIME, 0);
   3660 
   3661                 final VideoEditorProject videoProject = getProject(projectPath);
   3662                 if (videoProject != null) {
   3663                     final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, overlayId);
   3664                     if (overlay != null) {
   3665                         videoProject.setClean(false);
   3666                         if (ex == null) {
   3667                             overlay.setStartTime(startTimeMs);
   3668                         } else {
   3669                             overlay.setAppStartTime(overlay.getStartTime());
   3670                         }
   3671                     }
   3672                 }
   3673 
   3674                 for (ApiServiceListener listener : mListeners) {
   3675                     listener.onOverlayStartTimeSet(projectPath, overlayId, mediaItemId,
   3676                             startTimeMs, ex);
   3677                 }
   3678 
   3679                 break;
   3680             }
   3681 
   3682             case OP_OVERLAY_SET_DURATION: {
   3683                 if (finalize) {
   3684                     finalizeRequest(intent);
   3685                 }
   3686 
   3687                 final String mediaItemId = intent.getStringExtra(
   3688                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3689                 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3690                 final long durationMs = intent.getLongExtra(PARAM_DURATION, 0);
   3691 
   3692                 final VideoEditorProject videoProject = getProject(projectPath);
   3693                 if (videoProject != null) {
   3694                     final MovieOverlay overlay = videoProject.getOverlay(mediaItemId, overlayId);
   3695                     if (overlay != null) {
   3696                         videoProject.setClean(false);
   3697                         if (ex == null) {
   3698                             overlay.setDuration(durationMs);
   3699                         } else {
   3700                             overlay.setAppDuration(overlay.getDuration());
   3701                         }
   3702                     }
   3703                 }
   3704 
   3705                 for (ApiServiceListener listener : mListeners) {
   3706                     listener.onOverlayDurationSet(projectPath, overlayId, mediaItemId,
   3707                             durationMs, ex);
   3708                 }
   3709 
   3710                 break;
   3711             }
   3712 
   3713             case OP_OVERLAY_SET_ATTRIBUTES: {
   3714                 if (finalize) {
   3715                     finalizeRequest(intent);
   3716                 }
   3717 
   3718                 final String mediaItemId = intent.getStringExtra(
   3719                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3720                 final String overlayId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3721                 final Bundle userAttributes = intent.getBundleExtra(PARAM_ATTRIBUTES);
   3722 
   3723                 final VideoEditorProject videoProject = getProject(projectPath);
   3724                 if (videoProject != null) {
   3725                     if (ex == null) {
   3726                         final MovieOverlay overlay = videoProject.getOverlay(mediaItemId,
   3727                                 overlayId);
   3728                         if (overlay != null) {
   3729                             videoProject.setClean(false);
   3730                             overlay.updateUserAttributes(userAttributes);
   3731                         }
   3732                     }
   3733                 }
   3734 
   3735                 for (ApiServiceListener listener : mListeners) {
   3736                     listener.onOverlayUserAttributesSet(projectPath, overlayId, mediaItemId,
   3737                             userAttributes, ex);
   3738                 }
   3739 
   3740                 break;
   3741             }
   3742 
   3743             case OP_EFFECT_ADD_COLOR:
   3744             case OP_EFFECT_ADD_IMAGE_KEN_BURNS:{
   3745                 if (finalize) {
   3746                     finalizeRequest(intent);
   3747                 }
   3748 
   3749                 final String mediaItemId = intent.getStringExtra(
   3750                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3751 
   3752                 final MovieEffect movieEffect = (MovieEffect)result;
   3753                 final VideoEditorProject videoProject = getProject(projectPath);
   3754                 if (videoProject != null) {
   3755                     if (ex == null) {
   3756                         videoProject.addEffect(mediaItemId, movieEffect);
   3757                     }
   3758                 }
   3759 
   3760                 for (ApiServiceListener listener : mListeners) {
   3761                     listener.onEffectAdded(projectPath, movieEffect, mediaItemId, ex);
   3762                 }
   3763 
   3764                 break;
   3765             }
   3766 
   3767             case OP_EFFECT_REMOVE: {
   3768                 if (finalize) {
   3769                     finalizeRequest(intent);
   3770                 }
   3771 
   3772                 final String mediaItemId = intent.getStringExtra(
   3773                         PARAM_RELATIVE_STORYBOARD_ITEM_ID);
   3774                 final String effectId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3775 
   3776                 final VideoEditorProject videoProject = getProject(projectPath);
   3777                 if (videoProject != null) {
   3778                     if (ex == null) {
   3779                         videoProject.removeEffect(mediaItemId, effectId);
   3780                     }
   3781                 }
   3782 
   3783                 for (ApiServiceListener listener : mListeners) {
   3784                     listener.onEffectRemoved(projectPath, effectId, mediaItemId, ex);
   3785                 }
   3786 
   3787                 break;
   3788             }
   3789 
   3790             case OP_AUDIO_TRACK_ADD: {
   3791                 if (finalize) {
   3792                     finalizeRequest(intent);
   3793                 }
   3794 
   3795                 final MovieAudioTrack movieAudioTrack = (MovieAudioTrack)result;
   3796                 final VideoEditorProject videoProject = getProject(projectPath);
   3797                 if (videoProject != null) {
   3798                     if (ex == null) {
   3799                         videoProject.addAudioTrack(movieAudioTrack);
   3800                     }
   3801                 }
   3802 
   3803                 for (ApiServiceListener listener : mListeners) {
   3804                     listener.onAudioTrackAdded(projectPath, movieAudioTrack, ex);
   3805                 }
   3806 
   3807                 break;
   3808             }
   3809 
   3810             case OP_AUDIO_TRACK_REMOVE: {
   3811                 if (finalize) {
   3812                     finalizeRequest(intent);
   3813                 }
   3814 
   3815                 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3816 
   3817                 final VideoEditorProject videoProject = getProject(projectPath);
   3818                 if (videoProject != null) {
   3819                     if (ex == null) {
   3820                         videoProject.removeAudioTrack(audioTrackId);
   3821                     }
   3822                 }
   3823 
   3824                 for (ApiServiceListener listener : mListeners) {
   3825                     listener.onAudioTrackRemoved(projectPath, audioTrackId, ex);
   3826                 }
   3827 
   3828                 break;
   3829             }
   3830 
   3831             case OP_AUDIO_TRACK_SET_BOUNDARIES: {
   3832                 if (finalize) {
   3833                     finalizeRequest(intent);
   3834                 }
   3835 
   3836                 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3837                 final long beginBoundary = intent.getLongExtra(PARAM_BEGIN_BOUNDARY, 0);
   3838                 final long endBoundary = intent.getLongExtra(PARAM_END_BOUNDARY, 0);
   3839 
   3840                 final VideoEditorProject videoProject = getProject(projectPath);
   3841                 if (videoProject != null) {
   3842                     if (ex == null) {
   3843                         final MovieAudioTrack audioTrack =
   3844                             videoProject.getAudioTrack(audioTrackId);
   3845                         if (audioTrack != null) {
   3846                             videoProject.setClean(false);
   3847                             audioTrack.setExtractBoundaries(beginBoundary, endBoundary);
   3848                         }
   3849                     }
   3850                 }
   3851 
   3852                 for (ApiServiceListener listener : mListeners) {
   3853                     listener.onAudioTrackBoundariesSet(projectPath, audioTrackId,
   3854                             beginBoundary, endBoundary, ex);
   3855                 }
   3856 
   3857                 break;
   3858             }
   3859 
   3860             case OP_AUDIO_TRACK_SET_LOOP: {
   3861                 if (finalize) {
   3862                     finalizeRequest(intent);
   3863                 }
   3864 
   3865                 final VideoEditorProject videoProject = getProject(projectPath);
   3866                 if (videoProject != null) {
   3867                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3868                     final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId);
   3869                     if (audioTrack != null) {
   3870                         videoProject.setClean(false);
   3871                         if (ex == null) {
   3872                             audioTrack.enableLoop(intent.getBooleanExtra(PARAM_LOOP, false));
   3873                         } else {
   3874                             audioTrack.enableAppLoop(audioTrack.isLooping());
   3875                         }
   3876                     }
   3877                 }
   3878 
   3879                 break;
   3880             }
   3881 
   3882             case OP_AUDIO_TRACK_SET_DUCK: {
   3883                 if (finalize) {
   3884                     finalizeRequest(intent);
   3885                 }
   3886 
   3887                 final VideoEditorProject videoProject = getProject(projectPath);
   3888                 if (videoProject != null) {
   3889                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3890                     final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId);
   3891                     if (audioTrack != null) {
   3892                         videoProject.setClean(false);
   3893                         if (ex == null) {
   3894                             audioTrack.enableDucking(intent.getBooleanExtra(PARAM_DUCK, false));
   3895                         } else {
   3896                             audioTrack.enableAppDucking(audioTrack.isDuckingEnabled());
   3897                         }
   3898                     }
   3899                 }
   3900 
   3901                 break;
   3902             }
   3903 
   3904             case OP_AUDIO_TRACK_SET_VOLUME: {
   3905                 if (finalize) {
   3906                     finalizeRequest(intent);
   3907                 }
   3908 
   3909                 final VideoEditorProject videoProject = getProject(projectPath);
   3910                 if (videoProject != null) {
   3911                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3912                     final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId);
   3913                     if (audioTrack != null) {
   3914                         videoProject.setClean(false);
   3915                         if (ex == null) {
   3916                             audioTrack.setVolume(intent.getIntExtra(PARAM_VOLUME, 0));
   3917                         } else {
   3918                             audioTrack.setAppVolume(audioTrack.getVolume());
   3919                         }
   3920                     }
   3921                 }
   3922 
   3923                 break;
   3924             }
   3925 
   3926             case OP_AUDIO_TRACK_SET_MUTE: {
   3927                 if (finalize) {
   3928                     finalizeRequest(intent);
   3929                 }
   3930 
   3931                 final VideoEditorProject videoProject = getProject(projectPath);
   3932                 if (videoProject != null) {
   3933                     final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3934                     final MovieAudioTrack audioTrack = videoProject.getAudioTrack(audioTrackId);
   3935                     if (audioTrack != null) {
   3936                         videoProject.setClean(false);
   3937                         if (ex == null) {
   3938                             audioTrack.setMute(intent.getBooleanExtra(PARAM_MUTE, false));
   3939                         } else {
   3940                             audioTrack.setAppMute(audioTrack.isMuted());
   3941                         }
   3942                     }
   3943                 }
   3944 
   3945                 break;
   3946             }
   3947 
   3948             case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS: {
   3949                 if (finalize) {
   3950                     finalizeRequest(intent);
   3951                 }
   3952 
   3953                 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3954                 final int progress = intent.getIntExtra(PARAM_PROGRESS_VALUE, 0);
   3955 
   3956                 for (ApiServiceListener listener : mListeners) {
   3957                     listener.onAudioTrackExtractAudioWaveformProgress(projectPath, audioTrackId,
   3958                             progress);
   3959                 }
   3960 
   3961                 break;
   3962             }
   3963 
   3964             case OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM: {
   3965                 if (finalize) {
   3966                     finalizeRequest(intent);
   3967                 }
   3968 
   3969                 final String audioTrackId = intent.getStringExtra(PARAM_STORYBOARD_ITEM_ID);
   3970 
   3971                 final VideoEditorProject videoProject = getProject(projectPath);
   3972                 if (ex == null && videoProject != null) {
   3973                     if (result != null) {
   3974                         final MovieAudioTrack audioTrack =
   3975                             videoProject.getAudioTrack(audioTrackId);
   3976                         if (audioTrack != null) {
   3977                             videoProject.setClean(false);
   3978                             audioTrack.setWaveformData((WaveformData)result);
   3979                         }
   3980                     }
   3981                 }
   3982 
   3983                 for (ApiServiceListener listener : mListeners) {
   3984                     listener.onAudioTrackExtractAudioWaveformComplete(projectPath,
   3985                             audioTrackId, ex);
   3986                 }
   3987 
   3988                 break;
   3989             }
   3990 
   3991             default: {
   3992                 if (finalize) {
   3993                     finalizeRequest(intent);
   3994                 }
   3995                 break;
   3996             }
   3997         }
   3998 
   3999         if (finalize) {
   4000             mIntentPool.put(intent);
   4001         }
   4002     }
   4003 
   4004     /**
   4005      * Finalizes a request. Calls the listeners that are interested in project status
   4006      * change and stops this service if there are no more pending intents.
   4007      *
   4008      * @param intent The intent that just completed
   4009      */
   4010     private void finalizeRequest(Intent intent) {
   4011         mPendingIntents.remove(intent.getStringExtra(PARAM_REQUEST_ID));
   4012 
   4013         final String projectPath = intent.getStringExtra(PARAM_PROJECT_PATH);
   4014         if (projectPath != null) {
   4015             final boolean projectEdited = isProjectBeingEdited(projectPath);
   4016             if (projectEdited == false) {
   4017                 for (ApiServiceListener listener : mListeners) {
   4018                     listener.onProjectEditState(projectPath, projectEdited);
   4019                 }
   4020             }
   4021         }
   4022 
   4023         if (mPendingIntents.size() == 0) {
   4024             // Cancel the current timer if any. Extend the timeout by 5000 ms.
   4025             mHandler.removeCallbacks(mStopRunnable);
   4026 
   4027             // Start a timer which will stop the service if the queue of
   4028             // pending intent will be empty at that time.
   4029             // This prevents the service from starting & stopping too often.
   4030             mHandler.postDelayed(mStopRunnable, 5000);
   4031             logd("completeRequest: Stopping service in 5000 ms");
   4032         }
   4033     }
   4034 
   4035     /**
   4036      * Checks if the current project is the project specified by the specified path.
   4037      *
   4038      * @param projectPath The project path
   4039      *
   4040      * @return The video editor project
   4041      */
   4042     private VideoEditorProject getProject(String projectPath) {
   4043         if (mVideoProject != null) {
   4044             if (mVideoProject.getPath().equals(projectPath)) {
   4045                 return mVideoProject;
   4046             }
   4047         }
   4048 
   4049         return null;
   4050     }
   4051 
   4052     /**
   4053      * Check if the current editor is the project specified by the specified path
   4054      *
   4055      * @param projectPath The project path
   4056      *
   4057      * @return The video editor
   4058      */
   4059     private synchronized VideoEditor getVideoEditor(String projectPath) {
   4060         if (mVideoEditor != null) {
   4061             if (mVideoEditor.getPath().equals(projectPath)) {
   4062                 return mVideoEditor;
   4063             }
   4064         }
   4065 
   4066         return null;
   4067     }
   4068 
   4069     /**
   4070      * Release the editor
   4071      */
   4072     private synchronized void releaseEditor() {
   4073         if (mVideoEditor != null) {
   4074             logd("releaseEditor (current): " + mVideoEditor.getPath());
   4075             mVideoEditor.release();
   4076             mVideoEditor = null;
   4077             mGeneratePreviewListener = null;
   4078 
   4079             System.gc();
   4080         }
   4081     }
   4082 
   4083     /**
   4084      * Release the specified editor
   4085      *
   4086      * @param projectPath The project path
   4087      */
   4088     private synchronized void releaseEditor(String projectPath) {
   4089         if (mVideoEditor != null) {
   4090             if (mVideoEditor.getPath().equals(projectPath)) {
   4091                 logd("releaseEditor: " + projectPath);
   4092                 mVideoEditor.release();
   4093                 mVideoEditor = null;
   4094                 mGeneratePreviewListener = null;
   4095 
   4096                 System.gc();
   4097             }
   4098         }
   4099     }
   4100 
   4101     /**
   4102      * Release the current editor if it is *not* the current editor
   4103      *
   4104      * @param projectPath The project path
   4105      *
   4106      * @return The current video editor
   4107      */
   4108     private synchronized VideoEditor releaseEditorNot(String projectPath) {
   4109         if (mVideoEditor != null) {
   4110             if (!mVideoEditor.getPath().equals(projectPath)) {
   4111                 logd("releaseEditorNot: " + mVideoEditor.getPath());
   4112                 mVideoEditor.release();
   4113                 mVideoEditor = null;
   4114                 mGeneratePreviewListener = null;
   4115 
   4116                 System.gc();
   4117             }
   4118         }
   4119 
   4120         return mVideoEditor;
   4121     }
   4122 
   4123     /**
   4124      * Generate the preview
   4125      *
   4126      * @param videoEditor The video editor
   4127      * @param updatePreviewFrame true to show preview frame when done
   4128      */
   4129     private void generatePreview(VideoEditor videoEditor, boolean updatePreviewFrame) {
   4130         try {
   4131             videoEditor.generatePreview(mGeneratePreviewListener);
   4132             if (mGeneratePreviewListener != null) {
   4133                 // This is the last callback which is always fired last to
   4134                 // let the UI know that generate preview completed
   4135                 mGeneratePreviewListener.onProgress(null,
   4136                         updatePreviewFrame ? ACTION_UPDATE_FRAME : ACTION_NO_FRAME_UPDATE, 100);
   4137             }
   4138         } catch (Exception ex) {
   4139             ex.printStackTrace();
   4140         }
   4141     }
   4142 
   4143     /**
   4144      * Exports a movie in a distinct worker thread.
   4145      *
   4146      * @param videoEditor The video editor
   4147      * @param intent The intent
   4148      */
   4149     private void exportMovie(final VideoEditor videoEditor, final Intent intent) {
   4150         mExportCancelled = false;
   4151         new Thread() {
   4152             @Override
   4153             public void run() {
   4154                 final String filename = intent.getStringExtra(PARAM_FILENAME);
   4155                 final int height = intent.getIntExtra(PARAM_HEIGHT, -1);
   4156                 final int bitrate = intent.getIntExtra(PARAM_BITRATE, -1);
   4157 
   4158                 // Create the export status Intent
   4159                 final Intent statusIntent = mIntentPool.get();
   4160                 statusIntent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT_STATUS);
   4161                 statusIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra(
   4162                         PARAM_PROJECT_PATH));
   4163                 statusIntent.putExtra(PARAM_FILENAME, filename);
   4164                 statusIntent.putExtra(PARAM_INTENT, intent);
   4165                 Exception resultException = null;
   4166 
   4167                 try {
   4168                     videoEditor.export(filename, height, bitrate, new ExportProgressListener() {
   4169                         @Override
   4170                         public void onProgress(VideoEditor videoEditor, String filename,
   4171                                 int progress) {
   4172                             final Intent progressIntent = mIntentPool.get();
   4173                             progressIntent.putExtra(PARAM_OP, OP_VIDEO_EDITOR_EXPORT_STATUS);
   4174                             progressIntent.putExtra(PARAM_PROJECT_PATH, intent.getStringExtra(
   4175                                     PARAM_PROJECT_PATH));
   4176                             progressIntent.putExtra(PARAM_FILENAME, filename);
   4177                             progressIntent.putExtra(PARAM_INTENT, intent);
   4178                             progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress);
   4179                             mVideoThread.submit(progressIntent);
   4180                             logv("Export progress: " + progress + " for: " + filename);
   4181                         }
   4182                     });
   4183 
   4184                     statusIntent.putExtra(PARAM_CANCELLED, mExportCancelled);
   4185                     if (!mExportCancelled) {
   4186                         if (new File(filename).exists()) {
   4187                             statusIntent.putExtra(PARAM_MOVIE_URI, exportToGallery(filename));
   4188                         } else {
   4189                             resultException = new IllegalStateException("Export file does not exist: " + filename);
   4190                         }
   4191                         logv("Export complete for: " + filename);
   4192                     } else {
   4193                         logv("Export cancelled by user, file name: " + filename);
   4194                     }
   4195                 } catch (Exception ex) {
   4196                     logv("Export error for: " + filename);
   4197                     ex.printStackTrace();
   4198                     resultException = ex;
   4199                 }
   4200 
   4201                 // Complete the request
   4202                 statusIntent.putExtra(PARAM_EXCEPTION, resultException);
   4203                 mVideoThread.submit(statusIntent);
   4204             }
   4205         }.start();
   4206     }
   4207 
   4208     /**
   4209      * Extract the audio waveform of a media item
   4210      *
   4211      * @param intent The original Intent
   4212      * @param videoEditor The video editor
   4213      * @param mediaItem The media item
   4214      */
   4215     private void extractMediaItemAudioWaveform(final Intent intent, final VideoEditor videoEditor,
   4216             final MediaVideoItem mediaItem) throws IOException {
   4217         mediaItem.extractAudioWaveform(
   4218             new ExtractAudioWaveformProgressListener() {
   4219             @Override
   4220             public void onProgress(int progress) {
   4221                 final Intent progressIntent = mIntentPool.get();
   4222                 progressIntent.putExtra(PARAM_OP, OP_MEDIA_ITEM_EXTRACT_AUDIO_WAVEFORM_STATUS);
   4223                 progressIntent.putExtra(PARAM_PROJECT_PATH,
   4224                         intent.getStringExtra(PARAM_PROJECT_PATH));
   4225                 progressIntent.putExtra(PARAM_INTENT, intent);
   4226                 progressIntent.putExtra(PARAM_STORYBOARD_ITEM_ID, mediaItem.getId());
   4227                 progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress);
   4228 
   4229                 completeRequest(progressIntent, videoEditor, null, null, null, true);
   4230             }
   4231         });
   4232     }
   4233 
   4234     /**
   4235      * Extract the audio waveform of an AudioTrack
   4236      *
   4237      * @param intent The original Intent
   4238      * @param videoEditor The video editor
   4239      * @param audioTrack The audio track
   4240      */
   4241     private void extractAudioTrackAudioWaveform(final Intent intent, final VideoEditor videoEditor,
   4242             final AudioTrack audioTrack) throws IOException {
   4243         audioTrack.extractAudioWaveform(
   4244             new ExtractAudioWaveformProgressListener() {
   4245             @Override
   4246             public void onProgress(int progress) {
   4247                 final Intent progressIntent = mIntentPool.get();
   4248                 progressIntent.putExtra(PARAM_OP,
   4249                         OP_AUDIO_TRACK_EXTRACT_AUDIO_WAVEFORM_STATUS);
   4250                 progressIntent.putExtra(PARAM_PROJECT_PATH,
   4251                         intent.getStringExtra(PARAM_PROJECT_PATH));
   4252                 progressIntent.putExtra(PARAM_INTENT, intent);
   4253                 progressIntent.putExtra(PARAM_STORYBOARD_ITEM_ID, audioTrack.getId());
   4254                 progressIntent.putExtra(PARAM_PROGRESS_VALUE, progress);
   4255 
   4256                 completeRequest(progressIntent, videoEditor, null, null, null, true);
   4257             }
   4258         });
   4259     }
   4260 
   4261     /**
   4262      * Get the media item following the specified media item
   4263      *
   4264      * @param videoEditor The video editor
   4265      * @param mediaItemId The media item id
   4266      * @return The next media item
   4267      */
   4268     private static MediaItem nextMediaItem(VideoEditor videoEditor, String mediaItemId) {
   4269         final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   4270         if (mediaItemId == null) {
   4271             if (mediaItems.size() > 0) {
   4272                 return mediaItems.get(0);
   4273             }
   4274         } else {
   4275             final int mediaItemCount = mediaItems.size();
   4276             for (int i = 0; i < mediaItemCount; i++) {
   4277                 MediaItem mi = mediaItems.get(i);
   4278                 if (mi.getId().equals(mediaItemId)) {
   4279                     if (i < mediaItemCount - 1) {
   4280                         return mediaItems.get(i + 1);
   4281                     } else {
   4282                         break;
   4283                     }
   4284                 }
   4285             }
   4286         }
   4287 
   4288         return null;
   4289     }
   4290 
   4291     /**
   4292      * Export the movie to the Gallery
   4293      *
   4294      * @param filename The filename
   4295      * @return The video MediaStore URI
   4296      */
   4297     private Uri exportToGallery(String filename) {
   4298         // Save the name and description of a video in a ContentValues map.
   4299         final ContentValues values = new ContentValues(2);
   4300         values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4");
   4301         values.put(MediaStore.Video.Media.DATA, filename);
   4302         // Add a new record (identified by uri)
   4303         final Uri uri = getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI,
   4304                 values);
   4305         sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
   4306                 Uri.parse("file://"+ filename)));
   4307         return uri;
   4308     }
   4309 
   4310     /**
   4311      * Apply a theme to the entire movie. This method shall be used when the
   4312      * theme is changing.
   4313      *
   4314      * @param videoEditor The video editor
   4315      * @param themeId The theme id
   4316      */
   4317     private void applyThemeToMovie(VideoEditor videoEditor, String themeId) throws IOException {
   4318         final Context context = getApplicationContext();
   4319         final MovieTheme theme = MovieTheme.getTheme(context, themeId);
   4320         final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   4321 
   4322         // Add the transitions
   4323         final int mediaItemsCount = mediaItems.size();
   4324         if (mediaItemsCount > 0) {
   4325             // Remove all the transitions
   4326             for (int i = 0; i < mediaItemsCount; i++) {
   4327                 final MediaItem mi = mediaItems.get(i);
   4328                 if (i == 0) {
   4329                     final Transition beginTransition = mi.getBeginTransition();
   4330                     if (beginTransition != null) {
   4331                         videoEditor.removeTransition(beginTransition.getId());
   4332                     }
   4333                 }
   4334 
   4335                 final Transition endTransition = mi.getEndTransition();
   4336                 if (endTransition != null) {
   4337                     videoEditor.removeTransition(endTransition.getId());
   4338                 }
   4339             }
   4340 
   4341             // Add the begin transition to the first media item
   4342             final MovieTransition beginMovieTransition = theme.getBeginTransition();
   4343             if (beginMovieTransition != null) {
   4344                 final MediaItem firstMediaItem = mediaItems.get(0);
   4345                 videoEditor.addTransition(
   4346                         beginMovieTransition.buildTransition(context, null, firstMediaItem));
   4347             }
   4348 
   4349             // Add the mid transitions
   4350             final MovieTransition midMovieTransition = theme.getMidTransition();
   4351             if (midMovieTransition != null) {
   4352                 for (int i = 0; i < mediaItemsCount - 1; i++) {
   4353                     videoEditor.addTransition(
   4354                             midMovieTransition.buildTransition(context,
   4355                                     mediaItems.get(i), mediaItems.get(i + 1)));
   4356                 }
   4357             }
   4358 
   4359             // Add the end transition to the last media item
   4360             final MovieTransition endMovieTransition = theme.getEndTransition();
   4361             if (endMovieTransition != null) {
   4362                 final MediaItem lastMediaItem = mediaItems.get(mediaItemsCount - 1);
   4363                 videoEditor.addTransition(
   4364                         endMovieTransition.buildTransition(context, lastMediaItem, null));
   4365             }
   4366         }
   4367 
   4368         // Add the overlay
   4369         final MovieOverlay movieOverlay = theme.getOverlay();
   4370         if (movieOverlay != null && mediaItemsCount > 0) {
   4371             // Remove all the overlay for the first media item
   4372             final MediaItem mediaItem = mediaItems.get(0);
   4373             final List<Overlay> overlays = mediaItem.getAllOverlays();
   4374             if (overlays.size() > 0) {
   4375                 mediaItem.removeOverlay(overlays.get(0).getId());
   4376             }
   4377 
   4378             // Add the new overlay
   4379             final int scaledWidth, scaledHeight;
   4380             if (mediaItem instanceof MediaVideoItem) {
   4381                 scaledWidth = ((MediaVideoItem)mediaItem).getWidth();
   4382                 scaledHeight = ((MediaVideoItem)mediaItem).getHeight();
   4383             } else {
   4384                 scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth();
   4385                 scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight();
   4386             }
   4387 
   4388             final Overlay overlay = new OverlayFrame(mediaItem, generateId(),
   4389                     ImageUtils.buildOverlayBitmap(getApplicationContext(), null,
   4390                             movieOverlay.getType(), movieOverlay.getTitle(),
   4391                             movieOverlay.getSubtitle(), scaledWidth, scaledHeight),
   4392                             movieOverlay.getStartTime(), movieOverlay.getDuration());
   4393 
   4394             // Set the user attributes
   4395             final Bundle userAttributes = movieOverlay.buildUserAttributes();
   4396             for (String name : userAttributes.keySet()) {
   4397                 if (MovieOverlay.getAttributeType(name).equals(Integer.class)) {
   4398                     overlay.setUserAttribute(name,
   4399                             Integer.toString(userAttributes.getInt(name)));
   4400                 } else { // Strings
   4401                     overlay.setUserAttribute(name, userAttributes.getString(name));
   4402                 }
   4403             }
   4404             mediaItem.addOverlay(overlay);
   4405         }
   4406 
   4407         final MovieAudioTrack at = theme.getAudioTrack();
   4408         if (at != null) {
   4409             // Remove all audio tracks
   4410             final List<AudioTrack> audioTracks = videoEditor.getAllAudioTracks();
   4411             while (audioTracks.size() > 0) {
   4412                 videoEditor.removeAudioTrack(audioTracks.get(0).getId());
   4413             }
   4414 
   4415             // Add the new audio track
   4416             final AudioTrack audioTrack = new AudioTrack(videoEditor, generateId(),
   4417                     FileUtils.getAudioTrackFilename(context, at.getRawResourceId()));
   4418 
   4419             // Enable looping if necessary
   4420             if (at.isLooping()) {
   4421                 audioTrack.enableLoop();
   4422             }
   4423 
   4424             // Enable ducking
   4425             audioTrack.enableDucking(DUCK_THRESHOLD, DUCK_TRACK_VOLUME);
   4426             audioTrack.setVolume(DEFAULT_AUDIO_TRACK_VOLUME);
   4427             videoEditor.addAudioTrack(audioTrack);
   4428         }
   4429     }
   4430 
   4431     /**
   4432      * Apply a theme
   4433      *
   4434      * @param videoEditor The video editor
   4435      * @param themeId The theme id
   4436      * @param mediaItem The mediaItem
   4437      */
   4438     private void applyThemeToMediaItem(VideoEditor videoEditor, String themeId,
   4439             MediaItem mediaItem) throws IOException {
   4440         final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   4441         final int mediaItemsCount = mediaItems.size();
   4442         if (mediaItemsCount == 0) {
   4443             return;
   4444         }
   4445 
   4446         // We would only add transitions if the transitions don't exist
   4447         final Transition beginTransition = mediaItem.getBeginTransition();
   4448         final Transition endTransition = mediaItem.getEndTransition();
   4449 
   4450         final Context context = getApplicationContext();
   4451         final MovieTheme theme = MovieTheme.getTheme(context, themeId);
   4452 
   4453         final MediaItem firstMediaItem = mediaItems.get(0);
   4454         if (beginTransition == null) {
   4455             // Add the begin transition
   4456             final MovieTransition beginMovieTransition = theme.getBeginTransition();
   4457             if (beginMovieTransition != null) {
   4458                 if (firstMediaItem == mediaItem) {
   4459                     videoEditor.addTransition(
   4460                             beginMovieTransition.buildTransition(context, null, mediaItem));
   4461                 }
   4462             }
   4463         }
   4464 
   4465         // Add the mid transitions
   4466         final MovieTransition midMovieTransition = theme.getMidTransition();
   4467         if (midMovieTransition != null) {
   4468             for (int i = 0; i < mediaItemsCount; i++) {
   4469                 final MediaItem mi = mediaItems.get(i);
   4470                 if (mi == mediaItem) {
   4471                     if (i > 0) { // Not the first one
   4472                         if (beginTransition == null) {
   4473                             // Add transition before this media item
   4474                             videoEditor.addTransition(midMovieTransition.buildTransition(context,
   4475                                     mediaItems.get(i - 1), mi));
   4476                         }
   4477                     }
   4478 
   4479                     if (i < mediaItemsCount - 1) { // Not the last one
   4480                         if (endTransition == null) {
   4481                             // Add the transition after this media item
   4482                             videoEditor.addTransition(midMovieTransition.buildTransition(context,
   4483                                         mi, mediaItems.get(i + 1)));
   4484                         }
   4485                     }
   4486                     break;
   4487                 }
   4488             }
   4489         }
   4490 
   4491         if (endTransition == null) {
   4492             // Add the end transition to the last media item
   4493             final MovieTransition endMovieTransition = theme.getEndTransition();
   4494             final MediaItem lastMediaItem = mediaItems.get(mediaItemsCount - 1);
   4495             if (endMovieTransition != null && lastMediaItem == mediaItem) {
   4496                 videoEditor.addTransition(
   4497                         endMovieTransition.buildTransition(context, lastMediaItem, null));
   4498             }
   4499         }
   4500 
   4501         // Add the overlay
   4502         final MovieOverlay movieOverlay = theme.getOverlay();
   4503         if (movieOverlay != null) {
   4504             if (firstMediaItem == mediaItem) {
   4505                 // Remove the overlay
   4506                 final List<Overlay> overlays = mediaItem.getAllOverlays();
   4507                 if (overlays.size() > 0) {
   4508                     mediaItem.removeOverlay(overlays.get(0).getId());
   4509                 }
   4510 
   4511                 // Add the new overlay
   4512                 final int scaledWidth, scaledHeight;
   4513                 if (mediaItem instanceof MediaVideoItem) {
   4514                     scaledWidth = ((MediaVideoItem)mediaItem).getWidth();
   4515                     scaledHeight = ((MediaVideoItem)mediaItem).getHeight();
   4516                 } else {
   4517                     scaledWidth = ((MediaImageItem)mediaItem).getScaledWidth();
   4518                     scaledHeight = ((MediaImageItem)mediaItem).getScaledHeight();
   4519                 }
   4520 
   4521                 final Overlay overlay = new OverlayFrame(mediaItem, generateId(),
   4522                         ImageUtils.buildOverlayBitmap(getApplicationContext(), null,
   4523                                 movieOverlay.getType(), movieOverlay.getTitle(),
   4524                                 movieOverlay.getSubtitle(), scaledWidth, scaledHeight),
   4525                         movieOverlay.getStartTime(),
   4526                         Math.min(movieOverlay.getDuration(),
   4527                                 mediaItem.getDuration() - movieOverlay.getStartTime()));
   4528 
   4529                 // Set the user attributes
   4530                 final Bundle userAttributes = movieOverlay.buildUserAttributes();
   4531                 for (String name : userAttributes.keySet()) {
   4532                     if (MovieOverlay.getAttributeType(name).equals(Integer.class)) {
   4533                         overlay.setUserAttribute(name,
   4534                                 Integer.toString(userAttributes.getInt(name)));
   4535                     } else { // Strings
   4536                         overlay.setUserAttribute(name, userAttributes.getString(name));
   4537                     }
   4538                 }
   4539                 mediaItem.addOverlay(overlay);
   4540             }
   4541         }
   4542     }
   4543 
   4544     /**
   4545      * Apply theme transitions after an item was removed
   4546      *
   4547      * @param videoEditor The video editor
   4548      * @param themeId The theme id
   4549      * @param removedItemPosition The position of the removed item
   4550      * @param beginTransition The removed item begin transition
   4551      * @param endTransition The removed item end transition
   4552      *
   4553      * @return The transition that was added
   4554      */
   4555     private Transition applyThemeAfterRemove(VideoEditor videoEditor, String themeId,
   4556             int removedItemPosition, Transition beginTransition,
   4557             Transition endTransition) throws IOException {
   4558         final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   4559         final int mediaItemCount = mediaItems.size();
   4560         if (mediaItemCount == 0) {
   4561             return null;
   4562         }
   4563 
   4564         final Context context = getApplicationContext();
   4565         final MovieTheme theme = MovieTheme.getTheme(context, themeId);
   4566 
   4567         Transition transition = null;
   4568         if (removedItemPosition == 0) { // First item removed
   4569             if (theme.getBeginTransition() != null && beginTransition != null) {
   4570                 transition =
   4571                     theme.getBeginTransition().buildTransition(context, null, mediaItems.get(0));
   4572                 videoEditor.addTransition(transition);
   4573             }
   4574         } else if (removedItemPosition == mediaItemCount) { // Last item removed
   4575             if (theme.getEndTransition() != null && endTransition != null) {
   4576                 transition = theme.getEndTransition().buildTransition(context,
   4577                             mediaItems.get(mediaItemCount - 1), null);
   4578                 videoEditor.addTransition(transition);
   4579             }
   4580         } else { // Mid item removed
   4581             if (theme.getMidTransition() != null && beginTransition != null) {
   4582                 transition =
   4583                     theme.getMidTransition().buildTransition(context,
   4584                             mediaItems.get(removedItemPosition - 1),
   4585                             mediaItems.get(removedItemPosition));
   4586                 videoEditor.addTransition(transition);
   4587             }
   4588         }
   4589 
   4590         return transition;
   4591     }
   4592 
   4593     /**
   4594      * Apply theme transitions after an item was moved
   4595      *
   4596      * @param videoEditor The video editor
   4597      * @param themeId The theme id
   4598      * @param movedMediaItem The moved media item
   4599      * @param originalItemPosition The original media item position
   4600      * @param beginTransition The moved item begin transition
   4601      * @param endTransition The moved item end transition
   4602      */
   4603     private void applyThemeAfterMove(VideoEditor videoEditor, String themeId,
   4604             MediaItem movedMediaItem, int originalItemPosition, Transition beginTransition,
   4605             Transition endTransition) throws IOException {
   4606         final List<MediaItem> mediaItems = videoEditor.getAllMediaItems();
   4607         final int mediaItemCount = mediaItems.size();
   4608         if (mediaItemCount == 0) {
   4609             return;
   4610         }
   4611 
   4612         final Context context = getApplicationContext();
   4613         final MovieTheme theme = MovieTheme.getTheme(context, themeId);
   4614 
   4615         if (originalItemPosition == 0) { // First item moved
   4616             if (theme.getBeginTransition() != null && beginTransition != null) {
   4617                 final Transition transition =
   4618                     theme.getBeginTransition().buildTransition(context, null, mediaItems.get(0));
   4619                 videoEditor.addTransition(transition);
   4620             }
   4621         } else if (originalItemPosition == mediaItemCount - 1) { // Last item moved
   4622             if (theme.getEndTransition() != null && endTransition != null) {
   4623                 final Transition transition = theme.getEndTransition().buildTransition(context,
   4624                             mediaItems.get(mediaItemCount - 1), null);
   4625                 videoEditor.addTransition(transition);
   4626             }
   4627         } else { // Mid item moved
   4628             final int newPosition = mediaItems.indexOf(movedMediaItem);
   4629             if (newPosition > originalItemPosition) { // Moved forward
   4630                 if (endTransition != null && theme.getMidTransition() != null) {
   4631                     final Transition transition = theme.getMidTransition().buildTransition(
   4632                             context, mediaItems.get(originalItemPosition - 1),
   4633                             mediaItems.get(originalItemPosition));
   4634                     videoEditor.addTransition(transition);
   4635                 }
   4636             } else { // Moved backward
   4637                 if (beginTransition != null && theme.getMidTransition() != null) {
   4638                     final Transition transition = theme.getMidTransition().buildTransition(
   4639                             context, mediaItems.get(originalItemPosition),
   4640                                 mediaItems.get(originalItemPosition + 1));
   4641                     videoEditor.addTransition(transition);
   4642                 }
   4643             }
   4644         }
   4645 
   4646         // Apply the theme at the new position
   4647         applyThemeToMediaItem(videoEditor, themeId, movedMediaItem);
   4648     }
   4649 
   4650     /**
   4651      * Copy the media items
   4652      *
   4653      * @param mediaItems The media items
   4654      *
   4655      * @return The list of media items
   4656      */
   4657     private List<MovieMediaItem> copyMediaItems(List<MediaItem> mediaItems) {
   4658         final List<MovieMediaItem> movieMediaItems
   4659             = new ArrayList<MovieMediaItem>(mediaItems.size());
   4660         MovieMediaItem prevMediaItem = null;
   4661         for (MediaItem mediaItem : mediaItems) {
   4662             final MovieTransition prevTransition;
   4663             if (prevMediaItem != null) {
   4664                 prevTransition = prevMediaItem.getEndTransition();
   4665             } else if (mediaItem.getBeginTransition() != null) {
   4666                 prevTransition = new MovieTransition(mediaItem.getBeginTransition());
   4667             } else {
   4668                 prevTransition = null;
   4669             }
   4670 
   4671             final MovieMediaItem movieMediaItem = new MovieMediaItem(mediaItem, prevTransition);
   4672             movieMediaItems.add(movieMediaItem);
   4673             prevMediaItem = movieMediaItem;
   4674         }
   4675 
   4676         return movieMediaItems;
   4677     }
   4678 
   4679     /**
   4680      * Copy the audio tracks
   4681      *
   4682      * @param audioTracks The audio tracks
   4683      *
   4684      * @return The list of audio tracks
   4685      */
   4686     private List<MovieAudioTrack> copyAudioTracks(List<AudioTrack> audioTracks) {
   4687         final List<MovieAudioTrack> movieAudioTracks
   4688             = new ArrayList<MovieAudioTrack>(audioTracks.size());
   4689         for (AudioTrack audioTrack : audioTracks) {
   4690             movieAudioTracks.add(new MovieAudioTrack(audioTrack));
   4691         }
   4692         return movieAudioTracks;
   4693     }
   4694 
   4695     private static void logd(String message) {
   4696         if (Log.isLoggable(TAG, Log.DEBUG)) {
   4697             Log.d(TAG, message);
   4698         }
   4699     }
   4700 
   4701     private static void logv(String message) {
   4702         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   4703             Log.v(TAG, message);
   4704         }
   4705     }
   4706 
   4707     /**
   4708      * Worker thread that processes intents and maintains its own intent queue.
   4709      */
   4710     private class IntentProcessor extends Thread {
   4711         private final BlockingQueue<Intent> mIntentQueue;
   4712 
   4713         public IntentProcessor(String threadName) {
   4714             super("IntentProcessor-" + threadName);
   4715             mIntentQueue = new LinkedBlockingQueue<Intent>();
   4716         }
   4717 
   4718         @Override
   4719         public void run() {
   4720             try {
   4721                 while(true) {
   4722                     processIntent(mIntentQueue.take());
   4723                 }
   4724             } catch (InterruptedException e) {
   4725                 Log.e(TAG, "Terminating " + getName());
   4726             }
   4727         }
   4728 
   4729         /**
   4730          * Submits a new intent for processing.
   4731          *
   4732          * @param intent The intent to be processed
   4733          */
   4734         public void submit(Intent intent) {
   4735             if (isAlive()) {
   4736                 mIntentQueue.add(intent);
   4737             } else {
   4738                 Log.e(TAG, getName() + " should be started before submitting tasks.");
   4739             }
   4740         }
   4741 
   4742         /**
   4743          * Removes an intent from the queue.
   4744          *
   4745          * @param intent The intent to be removed
   4746          *
   4747          * @return true if the intent is removed
   4748          */
   4749         public boolean cancel(Intent intent) {
   4750             return mIntentQueue.remove(intent);
   4751         }
   4752 
   4753         public Iterator<Intent> getIntentQueueIterator() {
   4754             return mIntentQueue.iterator();
   4755         }
   4756 
   4757         public void quit() {
   4758             // Display an error if the queue is not empty and clear it.
   4759             final int queueSize = mIntentQueue.size();
   4760             if (queueSize > 0) {
   4761                 Log.e(TAG, "Thread queue is not empty. Size: " + queueSize);
   4762                 mIntentQueue.clear();
   4763             }
   4764             interrupt();
   4765         }
   4766     }
   4767 }
   4768