Home | History | Annotate | Download | only in widgets
      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.widgets;
     18 
     19 import java.util.List;
     20 
     21 import android.app.Activity;
     22 import android.app.Dialog;
     23 import android.content.Context;
     24 import android.content.DialogInterface;
     25 import android.content.Intent;
     26 import android.os.Bundle;
     27 import android.os.Handler;
     28 import android.util.AttributeSet;
     29 import android.util.Log;
     30 import android.view.ActionMode;
     31 import android.view.Display;
     32 import android.view.Menu;
     33 import android.view.MenuItem;
     34 import android.view.MotionEvent;
     35 import android.view.View;
     36 import android.widget.LinearLayout;
     37 import android.widget.RelativeLayout;
     38 
     39 import com.android.videoeditor.AlertDialogs;
     40 import com.android.videoeditor.OverlayTitleEditor;
     41 import com.android.videoeditor.VideoEditorActivity;
     42 import com.android.videoeditor.service.ApiService;
     43 import com.android.videoeditor.service.MovieMediaItem;
     44 import com.android.videoeditor.service.MovieOverlay;
     45 import com.android.videoeditor.service.VideoEditorProject;
     46 import com.android.videoeditor.util.FileUtils;
     47 import com.android.videoeditor.util.MediaItemUtils;
     48 import com.android.videoeditor.R;
     49 
     50 /**
     51  * LinearLayout which displays overlays.
     52  */
     53 public class OverlayLinearLayout extends LinearLayout {
     54     // Logging
     55     private static final String TAG = "OverlayLinearLayout";
     56 
     57     // Dialog parameter ids
     58     private static final String PARAM_DIALOG_MEDIA_ITEM_ID = "media_item_id";
     59 
     60     // Default overlay duration
     61     public static final long DEFAULT_TITLE_DURATION = 3000;
     62 
     63     // Instance variables
     64     private final ItemMoveGestureListener mOverlayGestureListener;
     65     private final int mHalfParentWidth;
     66     private final Handler mHandler;
     67     private final int mHandleWidth;
     68     private ActionMode mOverlayActionMode;
     69     private boolean mPlaybackInProgress;
     70     private VideoEditorProject mProject;
     71     private HandleView mLeftHandle, mRightHandle;
     72     private boolean mMoveLayoutPending;
     73     private View mResizingView;
     74 
     75     /**
     76      * The overlay listener
     77      */
     78     public interface OverlayLayoutListener {
     79         /**
     80          * Add a new overlay
     81          */
     82         public void onAddOverlay();
     83     }
     84 
     85     /**
     86      * The overlay action mode handler.
     87      */
     88     private class OverlayActionModeCallback implements ActionMode.Callback {
     89         // Instance variables
     90         private final MovieMediaItem mMediaItem;
     91 
     92         public OverlayActionModeCallback(MovieMediaItem mediaItem) {
     93             mMediaItem = mediaItem;
     94         }
     95 
     96         @Override
     97         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
     98             mOverlayActionMode = mode;
     99 
    100             final Activity activity = (Activity) getContext();
    101             activity.getMenuInflater().inflate(R.menu.overlay_mode_menu, menu);
    102             return true;
    103         }
    104 
    105         @Override
    106         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    107             final boolean enable = !ApiService.isProjectBeingEdited(mProject.getPath()) &&
    108                 !mPlaybackInProgress;
    109 
    110             final MenuItem eomi = menu.findItem(R.id.action_edit_overlay);
    111             eomi.setEnabled(enable);
    112 
    113             final MenuItem romi = menu.findItem(R.id.action_remove_overlay);
    114             romi.setEnabled(enable);
    115 
    116             return true;
    117         }
    118 
    119         @Override
    120         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    121             switch (item.getItemId()) {
    122                 case R.id.action_edit_overlay: {
    123                     final Activity activity = (Activity) getContext();
    124                     final Intent intent = new Intent(activity, OverlayTitleEditor.class);
    125                     intent.putExtra(OverlayTitleEditor.PARAM_MEDIA_ITEM_ID, mMediaItem.getId());
    126 
    127                     final MovieOverlay overlay = mMediaItem.getOverlay();
    128                     intent.putExtra(OverlayTitleEditor.PARAM_OVERLAY_ID, overlay.getId());
    129                     intent.putExtra(OverlayTitleEditor.PARAM_OVERLAY_ATTRIBUTES,
    130                             overlay.buildUserAttributes());
    131                     activity.startActivityForResult(intent,
    132                             VideoEditorActivity.REQUEST_CODE_PICK_OVERLAY);
    133                     break;
    134                 }
    135 
    136                 case R.id.action_remove_overlay: {
    137                     final Bundle bundle = new Bundle();
    138                     bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mMediaItem.getId());
    139                     ((Activity) getContext()).showDialog(
    140                             VideoEditorActivity.DIALOG_REMOVE_OVERLAY_ID, bundle);
    141                     break;
    142                 }
    143 
    144                 default: {
    145                     break;
    146                 }
    147             }
    148 
    149             return true;
    150         }
    151 
    152         @Override
    153         public void onDestroyActionMode(ActionMode mode) {
    154             final View overlayView = getOverlayView(mMediaItem.getId());
    155             if (overlayView != null) {
    156                 selectView(overlayView, false);
    157             }
    158 
    159             mOverlayActionMode = null;
    160         }
    161     }
    162 
    163     public OverlayLinearLayout(Context context, AttributeSet attrs, int defStyle) {
    164         super(context, attrs, defStyle);
    165 
    166         mOverlayGestureListener = new ItemMoveGestureListener() {
    167             private MovieMediaItem mScrollMediaItem;
    168             private MovieOverlay mScrollOverlay;
    169             private long mScrollTotalDurationMs, mScrollMediaItemStartTime;
    170             private boolean mScrolled;
    171 
    172             @Override
    173             public boolean onSingleTapConfirmed(View view, int area, MotionEvent e) {
    174                 if (mPlaybackInProgress) {
    175                     return false;
    176                 }
    177 
    178                 switch (((OverlayView)view).getState()) {
    179                     case OverlayView.STATE_STUB: {
    180                         unselectAllViews();
    181                         ((OverlayView)view).setState(OverlayView.STATE_ADD_BUTTON);
    182                         break;
    183                     }
    184 
    185                     case OverlayView.STATE_ADD_BUTTON: {
    186                         final MovieMediaItem mediaItem = (MovieMediaItem)view.getTag();
    187                         final Intent intent = new Intent(getContext(), OverlayTitleEditor.class);
    188                         intent.putExtra(OverlayTitleEditor.PARAM_MEDIA_ITEM_ID, mediaItem.getId());
    189                         ((Activity) getContext()).startActivityForResult(intent,
    190                                 VideoEditorActivity.REQUEST_CODE_PICK_OVERLAY);
    191                         break;
    192                     }
    193 
    194                     case OverlayView.STATE_OVERLAY: {
    195                         if (!view.isSelected()) {
    196                             selectView(view, true);
    197                         }
    198                         break;
    199                     }
    200 
    201                     default: {
    202                         break;
    203                     }
    204                 }
    205 
    206                 return true;
    207             }
    208 
    209             @Override
    210             public void onLongPress(View view, MotionEvent e) {
    211                 if (mPlaybackInProgress) {
    212                     return;
    213                 }
    214 
    215                 switch (((OverlayView)view).getState()) {
    216                     case OverlayView.STATE_STUB: {
    217                         break;
    218                     }
    219 
    220                     case OverlayView.STATE_ADD_BUTTON: {
    221                         break;
    222                     }
    223 
    224                     case OverlayView.STATE_OVERLAY: {
    225                         if (!view.isSelected()) {
    226                             selectView(view, true);
    227                         }
    228 
    229                         if (mOverlayActionMode == null) {
    230                             startActionMode(new OverlayActionModeCallback(
    231                                     (MovieMediaItem)view.getTag()));
    232                         }
    233                         break;
    234                     }
    235 
    236                     default: {
    237                         break;
    238                     }
    239                 }
    240             }
    241 
    242             @Override
    243             public boolean onMoveBegin(View view, MotionEvent e) {
    244                 if (mPlaybackInProgress) {
    245                     return false;
    246                 }
    247 
    248                 mScrollMediaItem = (MovieMediaItem)view.getTag();
    249                 mScrollMediaItemStartTime = mProject.getMediaItemBeginTime(mScrollMediaItem.getId());
    250                 mScrollOverlay = mScrollMediaItem.getOverlay();
    251                 // The duration of the timeline does not change while moving the Overlay
    252                 mScrollTotalDurationMs = mProject.computeDuration();
    253                 mRightHandle.setVisibility(View.GONE);
    254                 mScrolled = false;
    255                 return true;
    256             }
    257 
    258             @Override
    259             public boolean onMove(View view, MotionEvent e1, MotionEvent e2) {
    260                 final int beginPos = (int)(view.getLeft() - mHalfParentWidth - e1.getX() +
    261                         e2.getX());
    262                 long startTimeMs = ((beginPos * mScrollTotalDurationMs) /
    263                         (getWidth() - (2 * mHalfParentWidth)));
    264                 if (startTimeMs <= mScrollMediaItemStartTime) {
    265                     startTimeMs = mScrollMediaItemStartTime;
    266                 } else if (startTimeMs + mScrollOverlay.getAppDuration() >
    267                     mScrollMediaItemStartTime + mScrollMediaItem.getAppTimelineDuration()) {
    268                     startTimeMs = mScrollMediaItemStartTime +
    269                         mScrollMediaItem.getAppTimelineDuration() - mScrollOverlay.getAppDuration();
    270                 }
    271 
    272                 mScrolled = true;
    273                 mScrollOverlay.setAppStartTime(startTimeMs - mScrollMediaItemStartTime +
    274                         mScrollMediaItem.getAppBoundaryBeginTime());
    275                 requestLayout();
    276                 return true;
    277             }
    278 
    279             @Override
    280             public void onMoveEnd(View view) {
    281                 mRightHandle.setVisibility(View.VISIBLE);
    282                 if (mScrolled) {
    283                     mScrolled = false;
    284                     // Update the limits of the right handle
    285                     mRightHandle.setLimitReached(mScrollOverlay.getAppDuration() <=
    286                         MediaItemUtils.getMinimumMediaItemDuration(mScrollMediaItem),
    287                         mScrollOverlay.getAppStartTime() + mScrollOverlay.getAppDuration() >=
    288                             mScrollMediaItem.getAppBoundaryEndTime());
    289 
    290                     ApiService.setOverlayStartTime(getContext(), mProject.getPath(),
    291                             mScrollMediaItem.getId(), mScrollOverlay.getId(),
    292                             mScrollOverlay.getAppStartTime());
    293                 }
    294             }
    295         };
    296 
    297         // Add the beginning timeline item
    298         final View beginView = inflate(getContext(), R.layout.empty_timeline_item, null);
    299         beginView.setOnClickListener(new View.OnClickListener() {
    300             @Override
    301             public void onClick(View view) {
    302                 unselectAllViews();
    303             }
    304         });
    305         addView(beginView);
    306 
    307         // Add the end timeline item
    308         final View endView = inflate(getContext(), R.layout.empty_timeline_item, null);
    309         endView.setOnClickListener(new View.OnClickListener() {
    310             @Override
    311             public void onClick(View view) {
    312                 unselectAllViews();
    313             }
    314         });
    315         addView(endView);
    316 
    317         mLeftHandle = (HandleView)inflate(getContext(), R.layout.left_handle_view, null);
    318         addView(mLeftHandle);
    319 
    320         mRightHandle = (HandleView)inflate(getContext(), R.layout.right_handle_view, null);
    321         addView(mRightHandle);
    322 
    323         mHandleWidth = (int)context.getResources().getDimension(R.dimen.handle_width);
    324 
    325         // Compute half the width of the screen (and therefore the parent view)
    326         final Display display = ((Activity)context).getWindowManager().getDefaultDisplay();
    327         mHalfParentWidth = display.getWidth() / 2;
    328 
    329         mHandler = new Handler();
    330 
    331         setMotionEventSplittingEnabled(false);
    332    }
    333 
    334     public OverlayLinearLayout(Context context, AttributeSet attrs) {
    335         this(context, attrs, 0);
    336     }
    337 
    338     public OverlayLinearLayout(Context context) {
    339         this(context, null, 0);
    340     }
    341 
    342     /**
    343      * @param project The project
    344      */
    345     public void setProject(VideoEditorProject project) {
    346         // Close the contextual action bar
    347         if (mOverlayActionMode != null) {
    348             mOverlayActionMode.finish();
    349             mOverlayActionMode = null;
    350         }
    351 
    352         mLeftHandle.setVisibility(View.GONE);
    353         mLeftHandle.setListener(null);
    354         mRightHandle.setVisibility(View.GONE);
    355         mRightHandle.setListener(null);
    356 
    357         removeViews();
    358 
    359         mProject = project;
    360     }
    361 
    362     /**
    363      * @param inProgress true if playback is in progress
    364      */
    365     public void setPlaybackInProgress(boolean inProgress) {
    366         mPlaybackInProgress = inProgress;
    367 
    368         // Don't allow the user to interact with the overlays while playback
    369         // is in progress
    370         if (inProgress && mOverlayActionMode != null) {
    371             mOverlayActionMode.finish();
    372             mOverlayActionMode = null;
    373         }
    374     }
    375 
    376     /**
    377      * Add all the media items
    378      *
    379      * @param mediaItems The list of media items
    380      */
    381     public void addMediaItems(List<MovieMediaItem> mediaItems) {
    382         if (mOverlayActionMode != null) {
    383             mOverlayActionMode.finish();
    384             mOverlayActionMode = null;
    385         }
    386 
    387         removeViews();
    388 
    389         for (MovieMediaItem mediaItem : mediaItems) {
    390             addMediaItem(mediaItem);
    391         }
    392     }
    393 
    394     /**
    395      * Add a new media item at the end of the timeline
    396      *
    397      * @param mediaItem The media item
    398      * @return The view that was added
    399      */
    400     private View addMediaItem(MovieMediaItem mediaItem) {
    401         final OverlayView overlayView = (OverlayView)inflate(getContext(),
    402                 R.layout.overlay_item, null);
    403         if (mediaItem.getOverlay() != null) {
    404             overlayView.setState(OverlayView.STATE_OVERLAY);
    405         } else {
    406             overlayView.setState(OverlayView.STATE_STUB);
    407         }
    408 
    409         overlayView.setTag(mediaItem);
    410 
    411         overlayView.setGestureListener(mOverlayGestureListener);
    412 
    413         final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
    414                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT);
    415         addView(overlayView, getChildCount() - 1, lp);
    416 
    417         if (mOverlayActionMode != null) {
    418             mOverlayActionMode.invalidate();
    419         }
    420 
    421         requestLayout();
    422         return overlayView;
    423     }
    424 
    425     /**
    426      * Insert a new media item after the specified media item id
    427      *
    428      * @param mediaItem The media item
    429      * @param afterMediaItemId The id of the media item preceding the media item
    430      */
    431     public void insertMediaItem(MovieMediaItem mediaItem, String afterMediaItemId) {
    432         final OverlayView overlayView = (OverlayView)inflate(getContext(),
    433                 R.layout.overlay_item, null);
    434         if (mediaItem.getOverlay() != null) {
    435             overlayView.setState(OverlayView.STATE_OVERLAY);
    436         } else {
    437             overlayView.setState(OverlayView.STATE_STUB);
    438         }
    439 
    440         overlayView.setTag(mediaItem);
    441 
    442         overlayView.setGestureListener(mOverlayGestureListener);
    443 
    444         int insertViewIndex;
    445         if (afterMediaItemId != null) {
    446             if ((insertViewIndex = getMediaItemViewIndex(afterMediaItemId)) == -1) {
    447                 Log.e(TAG, "Media item not found: " + afterMediaItemId);
    448                 return;
    449             }
    450 
    451             insertViewIndex++;
    452         } else { // Insert at the beginning
    453             insertViewIndex = 1;
    454         }
    455 
    456         final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
    457                 LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT);
    458         addView(overlayView, insertViewIndex, lp);
    459 
    460         if (mOverlayActionMode != null) {
    461             mOverlayActionMode.invalidate();
    462         }
    463 
    464         requestLayout();
    465     }
    466 
    467     /**
    468      * Update media item
    469      *
    470      * @param mediaItem The media item
    471      */
    472     public void updateMediaItem(MovieMediaItem mediaItem) {
    473         final String mediaItemId = mediaItem.getId();
    474         final int childrenCount = getChildCount();
    475         for (int i = 0; i < childrenCount; i++) {
    476             final View childView = getChildAt(i);
    477             final MovieMediaItem mi = (MovieMediaItem)childView.getTag();
    478             if (mi != null && mediaItemId.equals(mi.getId())) {
    479                 if (mediaItem != mi) {
    480                     // The media item is a new instance
    481                     childView.setTag(mediaItem);
    482                 }
    483                 break;
    484             }
    485         }
    486 
    487         requestLayout();
    488         invalidate();
    489     }
    490 
    491     /**
    492      * Remove a media item
    493      *
    494      * @param mediaItemId The media item id
    495      * @return The view which was removed
    496      */
    497     public View removeMediaItem(String mediaItemId) {
    498         final int childrenCount = getChildCount();
    499         for (int i = 0; i < childrenCount; i++) {
    500             final View childView = getChildAt(i);
    501             final MovieMediaItem mediaItem = (MovieMediaItem)childView.getTag();
    502             if (mediaItem != null && mediaItem.getId().equals(mediaItemId)) {
    503                 removeViewAt(i);
    504                 requestLayout();
    505                 return childView;
    506             }
    507         }
    508 
    509         return null;
    510     }
    511 
    512     /**
    513      * A new overlay was added
    514      *
    515      * @param mediaItemId The media item which owns the overlay
    516      * @param overlay The overlay which was added
    517      */
    518     public void addOverlay(String mediaItemId, MovieOverlay overlay) {
    519         final OverlayView view = (OverlayView)getOverlayView(mediaItemId);
    520         if (view == null) {
    521             Log.e(TAG, "addOverlay: Media item not found: " + mediaItemId);
    522             return;
    523         }
    524 
    525         view.setState(OverlayView.STATE_OVERLAY);
    526 
    527         requestLayout();
    528         invalidate();
    529     }
    530 
    531     /**
    532      * An overlay was removed
    533      *
    534      * @param mediaItemId The media item which owns the overlay
    535      * @param overlayId The overlay id
    536      */
    537     public void removeOverlay(String mediaItemId, String overlayId) {
    538         final OverlayView view = (OverlayView)getOverlayView(mediaItemId);
    539         if (view == null) {
    540             Log.e(TAG, "removeOverlay: Media item not found: " + mediaItemId);
    541             return;
    542         }
    543 
    544         view.setState(OverlayView.STATE_STUB);
    545 
    546         requestLayout();
    547         invalidate();
    548 
    549         if (mOverlayActionMode != null) {
    550             mOverlayActionMode.finish();
    551             mOverlayActionMode = null;
    552         }
    553     }
    554 
    555     /**
    556      * Update the overlay attributes
    557      *
    558      * @param mediaItemId The media item id
    559      * @param overlayId The overlay id
    560      * @param userAttributes The overlay attributes
    561      */
    562     public void updateOverlayAttributes(String mediaItemId, String overlayId,
    563             Bundle userAttributes) {
    564         final MovieMediaItem mediaItem = mProject.getMediaItem(mediaItemId);
    565         if (mediaItem == null) {
    566             Log.e(TAG, "updateOverlayAttributes: Media item not found: " + mediaItemId);
    567             return;
    568         }
    569 
    570         final View overlayView = getOverlayView(mediaItemId);
    571         if (overlayView == null) {
    572             Log.e(TAG, "updateOverlayAttributes: Overlay not found: " + overlayId);
    573             return;
    574         }
    575 
    576         overlayView.invalidate();
    577     }
    578 
    579     /**
    580      * Refresh the view
    581      */
    582     public void refresh() {
    583         requestLayout();
    584         invalidate();
    585     }
    586 
    587     /**
    588      * Invalidate the CAB
    589      */
    590     public void invalidateCAB() {
    591         if (mOverlayActionMode != null) {
    592             mOverlayActionMode.invalidate();
    593         }
    594     }
    595 
    596     @Override
    597     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    598         final long totalDurationMs = mProject.computeDuration();
    599         final int viewWidth = getWidth() - (2 * mHalfParentWidth);
    600 
    601         final int leftViewWidth = (Integer)((View)getParent().getParent()).getTag(
    602                 R.id.left_view_width);
    603         long mediaItemStartTimeMs = 0;
    604         int left = 0;
    605         final int childrenCount = getChildCount();
    606         for (int i = 0; i < childrenCount; i++) {
    607             final View view = getChildAt(i);
    608             final MovieMediaItem mediaItem = (MovieMediaItem)view.getTag();
    609             if (mediaItem != null) { // Media item
    610                 final MovieOverlay overlay = mediaItem.getOverlay();
    611 
    612                 final int right;
    613                 if (overlay != null) {
    614                     // Note that this logic matches the one used in ApiService
    615                     // when handling the OP_MEDIA_ITEM_SET_BOUNDARIES command
    616                     if (overlay.getAppStartTime() <= mediaItem.getAppBoundaryBeginTime()) {
    617                         left = leftViewWidth + (int)((mediaItemStartTimeMs * viewWidth) /
    618                                 totalDurationMs);
    619                         final long durationMs = Math.min(overlay.getAppDuration(),
    620                                 mediaItem.getAppTimelineDuration());
    621                         right = leftViewWidth + (int)(((mediaItemStartTimeMs + durationMs) *
    622                                 viewWidth) / totalDurationMs);
    623                     } else if (overlay.getAppStartTime() + overlay.getAppDuration() >
    624                                 mediaItem.getAppBoundaryEndTime()) {
    625                         final long startTimeMs = Math.max(mediaItem.getAppBoundaryBeginTime(),
    626                                 mediaItem.getAppBoundaryEndTime() - overlay.getAppDuration());
    627                         left = leftViewWidth + (int)(((mediaItemStartTimeMs + startTimeMs -
    628                                 mediaItem.getAppBoundaryBeginTime()) * viewWidth) /
    629                                 totalDurationMs);
    630                         final long durationMs = mediaItem.getAppBoundaryEndTime() - startTimeMs;
    631                         right = leftViewWidth + (int)(((mediaItemStartTimeMs + startTimeMs -
    632                                 mediaItem.getAppBoundaryBeginTime() +
    633                                 durationMs) * viewWidth) / totalDurationMs);
    634                     } else {
    635                         left = leftViewWidth + (int)(((mediaItemStartTimeMs +
    636                                 overlay.getAppStartTime() - mediaItem.getAppBoundaryBeginTime()) *
    637                                 viewWidth) / totalDurationMs);
    638                         right = leftViewWidth + (int)(((mediaItemStartTimeMs
    639                                 + overlay.getAppStartTime() - mediaItem.getAppBoundaryBeginTime() +
    640                                 overlay.getAppDuration()) * viewWidth) / totalDurationMs);
    641                     }
    642                 } else {
    643                     left = leftViewWidth + (int)((mediaItemStartTimeMs * viewWidth) /
    644                             totalDurationMs);
    645                     right = leftViewWidth + (int)(((mediaItemStartTimeMs
    646                             + mediaItem.getAppTimelineDuration()) * viewWidth) / totalDurationMs);
    647                 }
    648 
    649                 view.layout(left, 0, right, b - t);
    650 
    651                 mediaItemStartTimeMs += mediaItem.getAppTimelineDuration();
    652                 if (mediaItem.getEndTransition() != null) {
    653                     mediaItemStartTimeMs -= mediaItem.getEndTransition().getAppDuration();
    654                 }
    655 
    656                 left = right;
    657             } else if (view == mLeftHandle) {
    658                 if (mResizingView != null) {
    659                     view.layout(mResizingView.getLeft() - mHandleWidth,
    660                             mResizingView.getPaddingTop(),
    661                             mResizingView.getLeft(), b - t - mResizingView.getPaddingBottom());
    662                 }
    663             } else if (view == mRightHandle) {
    664                 if (mResizingView != null) {
    665                     view.layout(mResizingView.getRight(), mResizingView.getPaddingTop(),
    666                             mResizingView.getRight() + mHandleWidth,
    667                             b - t - mResizingView.getPaddingBottom());
    668                 }
    669             } else if (i == 0) { // Begin view
    670                 view.layout(left, 0, left + leftViewWidth, b - t);
    671                 left += leftViewWidth;
    672             } else { // End view
    673                 view.layout(getWidth() - mHalfParentWidth - (mHalfParentWidth - leftViewWidth), 0,
    674                         getWidth(), b - t);
    675             }
    676         }
    677 
    678         mMoveLayoutPending = false;
    679     }
    680 
    681     /**
    682      * Create a new dialog
    683      *
    684      * @param id The dialog id
    685      * @param bundle The dialog bundle
    686      * @return The dialog
    687      */
    688     public Dialog onCreateDialog(int id, final Bundle bundle) {
    689         // If the project is not yet loaded do nothing.
    690         if (mProject == null) {
    691             return null;
    692         }
    693 
    694         switch (id) {
    695             case VideoEditorActivity.DIALOG_REMOVE_OVERLAY_ID: {
    696                 final MovieMediaItem mediaItem = mProject.getMediaItem(
    697                         bundle.getString(PARAM_DIALOG_MEDIA_ITEM_ID));
    698                 if (mediaItem == null) {
    699                     return null;
    700                 }
    701 
    702                 final Activity activity = (Activity) getContext();
    703                 return AlertDialogs.createAlert(activity, FileUtils.getSimpleName(
    704                         mediaItem.getFilename()), 0,
    705                         activity.getString(R.string.editor_remove_overlay_question),
    706                         activity.getString(R.string.yes),
    707                         new DialogInterface.OnClickListener() {
    708                     @Override
    709                     public void onClick(DialogInterface dialog, int which) {
    710                         if (mOverlayActionMode != null) {
    711                             mOverlayActionMode.finish();
    712                             mOverlayActionMode = null;
    713                         }
    714                         activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_OVERLAY_ID);
    715 
    716                         ApiService.removeOverlay(activity, mProject.getPath(), mediaItem.getId(),
    717                                 mediaItem.getOverlay().getId());
    718                     }
    719                 }, activity.getString(R.string.no), new DialogInterface.OnClickListener() {
    720                     @Override
    721                     public void onClick(DialogInterface dialog, int which) {
    722                         activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_OVERLAY_ID);
    723                     }
    724                 }, new DialogInterface.OnCancelListener() {
    725                     @Override
    726                     public void onCancel(DialogInterface dialog) {
    727                         activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_OVERLAY_ID);
    728                     }
    729                 }, true);
    730             }
    731 
    732             default: {
    733                 return null;
    734             }
    735         }
    736     }
    737 
    738     /**
    739      * Find the overlay view with the specified id
    740      *
    741      * @param mediaItemId The media item id
    742      * @return The overlay view
    743      */
    744     private View getOverlayView(String mediaItemId) {
    745         final int childrenCount = getChildCount();
    746         for (int i = 0; i < childrenCount; i++) {
    747             final View childView = getChildAt(i);
    748             final MovieMediaItem mediaItem = (MovieMediaItem)childView.getTag();
    749             if (mediaItem != null && mediaItemId.equals(mediaItem.getId())) {
    750                 return childView;
    751             }
    752         }
    753 
    754         return null;
    755     }
    756 
    757     /**
    758      * Find the media item view index with the specified id
    759      *
    760      * @param mediaItemId The media item id
    761      * @return The media item view index
    762      */
    763     private int getMediaItemViewIndex(String mediaItemId) {
    764         final int childrenCount = getChildCount();
    765         for (int i = 0; i < childrenCount; i++) {
    766             final View childView = getChildAt(i);
    767             final Object tag = childView.getTag();
    768             if (tag != null && tag instanceof MovieMediaItem) {
    769                 final MovieMediaItem mediaItem = (MovieMediaItem)tag;
    770                 if (mediaItemId.equals(mediaItem.getId())) {
    771                     return i;
    772                 }
    773             }
    774         }
    775 
    776         return -1;
    777     }
    778 
    779     /**
    780      * Remove all overlay views (leave the beginning and end views)
    781      */
    782     private void removeViews() {
    783         int index = 0;
    784         while (index < getChildCount()) {
    785             final MovieMediaItem mediaItem = (MovieMediaItem)getChildAt(index).getTag();
    786             if (mediaItem != null) {
    787                 removeViewAt(index);
    788             } else {
    789                 index++;
    790             }
    791         }
    792 
    793         requestLayout();
    794     }
    795 
    796     @Override
    797     public void setSelected(boolean selected) {
    798         if (selected == false) {
    799             // Close the contextual action bar
    800             if (mOverlayActionMode != null) {
    801                 mOverlayActionMode.finish();
    802                 mOverlayActionMode = null;
    803             }
    804 
    805             mLeftHandle.setVisibility(View.GONE);
    806             mLeftHandle.setListener(null);
    807             mRightHandle.setVisibility(View.GONE);
    808             mRightHandle.setListener(null);
    809             mResizingView = null;
    810         }
    811 
    812         final int childrenCount = getChildCount();
    813         for (int i = 0; i < childrenCount; i++) {
    814             final View childView = getChildAt(i);
    815             childView.setSelected(false);
    816         }
    817     }
    818 
    819     /**
    820      * Select a view and unselect any view that is selected.
    821      *
    822      * @param view The view to select
    823      * @param selected true if selected
    824      */
    825     private void selectView(final View selectedView, boolean selected) {
    826         // Check if the selection has changed
    827         if (selectedView.isSelected() == selected) {
    828             return;
    829         }
    830 
    831         if (selected == false) {
    832             // Select the new view
    833             selectedView.setSelected(selected);
    834             mResizingView = null;
    835             mLeftHandle.setVisibility(View.GONE);
    836             mLeftHandle.setListener(null);
    837             mRightHandle.setVisibility(View.GONE);
    838             mRightHandle.setListener(null);
    839             return;
    840         }
    841 
    842         // Unselect all other views
    843         unselectAllViews();
    844 
    845         // Select the new view
    846         selectedView.setSelected(selected);
    847 
    848         final Object tag = selectedView.getTag();
    849         final MovieMediaItem mediaItem = (MovieMediaItem)tag;
    850         if (mOverlayActionMode == null) {
    851             startActionMode(new OverlayActionModeCallback(mediaItem));
    852         }
    853 
    854         final MovieOverlay overlay = mediaItem.getOverlay();
    855         final View overlayView = getOverlayView(mediaItem.getId());
    856         mResizingView = overlayView;
    857 
    858         mRightHandle.setVisibility(View.VISIBLE);
    859         mRightHandle.bringToFront();
    860         mRightHandle.setLimitReached(overlay.getAppDuration() <=
    861             MediaItemUtils.getMinimumMediaItemDuration(mediaItem),
    862                 overlay.getAppStartTime() + overlay.getAppDuration() >=
    863                     mediaItem.getAppBoundaryEndTime());
    864         mRightHandle.setListener(new HandleView.MoveListener() {
    865             private MovieMediaItem mMediaItem;
    866             private int mMovePosition;
    867             private long mMinimumDurationMs;
    868 
    869             @Override
    870             public void onMoveBegin(HandleView view) {
    871                 mMediaItem = mediaItem;
    872                 mMinimumDurationMs = MediaItemUtils.getMinimumMediaItemDuration(mediaItem);
    873             }
    874 
    875             @Override
    876             public boolean onMove(HandleView view, int left, int delta) {
    877                 if (mMoveLayoutPending) {
    878                     return false;
    879                 }
    880 
    881                 final int position = left + delta;
    882                 // Compute what will become the width of the view
    883                 final int newWidth = position - overlayView.getLeft();
    884 
    885                 // Compute the new duration
    886                 long newDurationMs = (newWidth * mProject.computeDuration()) /
    887                         (getWidth() - (2 * mHalfParentWidth));
    888 
    889                 if (newDurationMs < mMinimumDurationMs) {
    890                     newDurationMs = mMinimumDurationMs;
    891                 } else if (overlay.getAppStartTime() + newDurationMs >
    892                         mMediaItem.getAppBoundaryEndTime()) {
    893                     newDurationMs = mMediaItem.getAppBoundaryEndTime() - overlay.getAppStartTime();
    894                 }
    895 
    896                 mRightHandle.setLimitReached(overlay.getAppDuration() <= mMinimumDurationMs,
    897                         overlay.getAppStartTime() + overlay.getAppDuration() >=
    898                             mMediaItem.getAppBoundaryEndTime());
    899                 overlay.setAppDuration(newDurationMs);
    900 
    901                 mMovePosition = position;
    902                 mMoveLayoutPending = true;
    903 
    904                 requestLayout();
    905 
    906                 return true;
    907             }
    908 
    909             @Override
    910             public void onMoveEnd(final HandleView view, final int left, final int delta) {
    911                 final int position = left + delta;
    912                 if (mMoveLayoutPending || (position != mMovePosition)) {
    913                     mHandler.post(new Runnable() {
    914                         @Override
    915                         public void run() {
    916                             if (mMoveLayoutPending) {
    917                                 mHandler.post(this);
    918                             } else if (position != mMovePosition) {
    919                                 if (onMove(view, left, delta)) {
    920                                     mHandler.post(this);
    921                                 } else {
    922                                     scaleDone();
    923                                 }
    924                             } else {
    925                                 scaleDone();
    926                             }
    927                         }
    928                     });
    929                 } else {
    930                     scaleDone();
    931                 }
    932             }
    933 
    934             /**
    935              * Scale is done
    936              */
    937             public void scaleDone() {
    938                 ApiService.setOverlayDuration(getContext(), mProject.getPath(),
    939                         mMediaItem.getId(), overlay.getId(), overlay.getAppDuration());
    940             }
    941         });
    942     }
    943 
    944     /**
    945      * Unselect all views
    946      */
    947     private void unselectAllViews() {
    948         ((RelativeLayout)getParent()).setSelected(false);
    949     }
    950 }
    951