1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.videoeditor.widgets;
18
19import java.util.List;
20
21import android.app.Activity;
22import android.app.Dialog;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.os.Bundle;
27import android.os.Handler;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.view.ActionMode;
31import android.view.Display;
32import android.view.Menu;
33import android.view.MenuItem;
34import android.view.MotionEvent;
35import android.view.View;
36import android.widget.LinearLayout;
37import android.widget.RelativeLayout;
38
39import com.android.videoeditor.AlertDialogs;
40import com.android.videoeditor.OverlayTitleEditor;
41import com.android.videoeditor.VideoEditorActivity;
42import com.android.videoeditor.service.ApiService;
43import com.android.videoeditor.service.MovieMediaItem;
44import com.android.videoeditor.service.MovieOverlay;
45import com.android.videoeditor.service.VideoEditorProject;
46import com.android.videoeditor.util.FileUtils;
47import com.android.videoeditor.util.MediaItemUtils;
48import com.android.videoeditor.R;
49
50/**
51 * LinearLayout which displays overlays.
52 */
53public 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