| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.videoeditor.widgets; |
| |
| import java.util.List; |
| |
| import android.app.Activity; |
| import android.app.AlertDialog; |
| import android.app.Dialog; |
| import android.content.ClipData; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.graphics.Bitmap; |
| import android.graphics.Rect; |
| import android.media.videoeditor.EffectColor; |
| import android.media.videoeditor.MediaItem; |
| import android.media.videoeditor.Transition; |
| import android.media.videoeditor.TransitionSliding; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.util.AttributeSet; |
| import android.util.Log; |
| import android.view.ActionMode; |
| import android.view.Display; |
| import android.view.DragEvent; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.widget.ImageButton; |
| import android.widget.LinearLayout; |
| import android.widget.RelativeLayout; |
| import android.widget.Toast; |
| |
| import com.android.videoeditor.AlertDialogs; |
| import com.android.videoeditor.EffectType; |
| import com.android.videoeditor.KenBurnsActivity; |
| import com.android.videoeditor.OverlayTitleEditor; |
| import com.android.videoeditor.TransitionType; |
| import com.android.videoeditor.TransitionsActivity; |
| import com.android.videoeditor.VideoEditorActivity; |
| import com.android.videoeditor.service.ApiService; |
| import com.android.videoeditor.service.MovieEffect; |
| import com.android.videoeditor.service.MovieMediaItem; |
| import com.android.videoeditor.service.MovieOverlay; |
| import com.android.videoeditor.service.MovieTransition; |
| import com.android.videoeditor.service.VideoEditorProject; |
| import com.android.videoeditor.util.FileUtils; |
| import com.android.videoeditor.util.MediaItemUtils; |
| import com.android.videoeditor.R; |
| |
| /** |
| * LinearLayout which holds media items and transitions. |
| */ |
| public class MediaLinearLayout extends LinearLayout { |
| // Logging |
| private static final String TAG = "MediaLinearLayout"; |
| |
| // Dialog parameter ids |
| private static final String PARAM_DIALOG_MEDIA_ITEM_ID = "media_item_id"; |
| private static final String PARAM_DIALOG_CURRENT_RENDERING_MODE = "rendering_mode"; |
| private static final String PARAM_DIALOG_TRANSITION_ID = "transition_id"; |
| |
| // Transition duration limits |
| private static final long MAXIMUM_IMAGE_DURATION = 6000; |
| private static final long MAXIMUM_TRANSITION_DURATION = 3000; |
| private static final long MINIMUM_TRANSITION_DURATION = 250; |
| |
| private static final long TIME_TOLERANCE = 30; |
| |
| // Instance variables |
| private final ItemSimpleGestureListener mMediaItemGestureListener; |
| private final ItemSimpleGestureListener mTransitionGestureListener; |
| private final Handler mHandler; |
| private final int mHalfParentWidth; |
| private final int mHandleWidth; |
| private final int mTransitionVerticalInset; |
| private final ImageButton mLeftAddClipButton, mRightAddClipButton; |
| private MediaLinearLayoutListener mListener; |
| private ActionMode mMediaItemActionMode; |
| private ActionMode mTransitionActionMode; |
| private VideoEditorProject mProject; |
| private boolean mPlaybackInProgress; |
| private HandleView mLeftHandle, mRightHandle; |
| private boolean mIsTrimming; // Indicates if some media item is being trimmed. |
| private boolean mMoveLayoutPending; |
| private View mScrollView; // Convenient handle to the parent scroll view. |
| private View mSelectedView; |
| private String mDragMediaItemId; |
| private float mPrevDragPosition; |
| private long mPrevDragScrollTime; |
| private MovieMediaItem mDropAfterMediaItem; |
| private int mDropIndex; |
| private boolean mFirstEntered; |
| |
| /** |
| * The media item action mode handler. |
| */ |
| private class MediaItemActionModeCallback implements ActionMode.Callback { |
| // Media item associated with this callback. |
| private final MovieMediaItem mMediaItem; |
| |
| public MediaItemActionModeCallback(MovieMediaItem mediaItem) { |
| mMediaItem = mediaItem; |
| } |
| |
| @Override |
| public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| mMediaItemActionMode = mode; |
| |
| final Activity activity = (Activity) getContext(); |
| activity.getMenuInflater().inflate(R.menu.media_item_mode_menu, menu); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| final boolean enable = !ApiService.isProjectBeingEdited(mProject.getPath()) && |
| !mPlaybackInProgress; |
| |
| // Pan zoom effect is only for images. Hide it from video clips. |
| MenuItem item; |
| if (!mMediaItem.isImage()) { |
| item = menu.findItem(R.id.action_pan_zoom_effect); |
| item.setVisible(false); |
| item.setEnabled(false); |
| } |
| |
| // If the selected media item already has an effect applied on it, check the |
| // corresponding effect menu item. |
| MovieEffect effect = mMediaItem.getEffect(); |
| if (effect != null) { |
| switch (mMediaItem.getEffect().getType()) { |
| case EffectType.EFFECT_KEN_BURNS: |
| item = menu.findItem(R.id.action_pan_zoom_effect); |
| break; |
| case EffectType.EFFECT_COLOR_GRADIENT: |
| item = menu.findItem(R.id.action_gradient_effect); |
| break; |
| case EffectType.EFFECT_COLOR_SEPIA: |
| item = menu.findItem(R.id.action_sepia_effect); |
| break; |
| case EffectType.EFFECT_COLOR_NEGATIVE: |
| item = menu.findItem(R.id.action_negative_effect); |
| break; |
| default: |
| item = menu.findItem(R.id.action_no_effect); |
| break; |
| } |
| } else { |
| item = menu.findItem(R.id.action_no_effect); |
| } |
| item.setChecked(true); |
| menu.findItem(R.id.media_item_effect_menu).setEnabled(enable); |
| |
| // Menu item for adding a new overlay. It is also used to edit |
| // existing overlay. We change the displayed text accordingly. |
| final MenuItem aomi = menu.findItem(R.id.action_add_overlay); |
| aomi.setTitle((mMediaItem.getOverlay() == null) ? |
| R.string.editor_add_overlay : R.string.editor_edit_overlay); |
| aomi.setEnabled(enable); |
| |
| final MenuItem romi = menu.findItem(R.id.action_remove_overlay); |
| romi.setVisible(mMediaItem.getOverlay() != null); |
| romi.setEnabled(enable && mMediaItem.getOverlay() != null); |
| |
| final MenuItem btmi = menu.findItem(R.id.action_add_begin_transition); |
| btmi.setVisible(mMediaItem.getBeginTransition() == null); |
| btmi.setEnabled(enable && mMediaItem.getBeginTransition() == null); |
| |
| final MenuItem etmi = menu.findItem(R.id.action_add_end_transition); |
| etmi.setVisible(mMediaItem.getEndTransition() == null); |
| etmi.setEnabled(enable && mMediaItem.getEndTransition() == null); |
| |
| final MenuItem rmmi = menu.findItem(R.id.action_rendering_mode); |
| rmmi.setVisible(mProject.hasMultipleAspectRatios()); |
| rmmi.setEnabled(enable && mProject.hasMultipleAspectRatios()); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.action_add_overlay: { |
| editOverlay(mMediaItem); |
| break; |
| } |
| |
| case R.id.action_remove_overlay: { |
| removeOverlay(mMediaItem); |
| break; |
| } |
| |
| case R.id.action_add_begin_transition: { |
| final MovieMediaItem prevMediaItem = mProject.getPreviousMediaItem( |
| mMediaItem.getId()); |
| pickTransition(prevMediaItem); |
| break; |
| } |
| |
| case R.id.action_add_end_transition: { |
| pickTransition(mMediaItem); |
| break; |
| } |
| |
| case R.id.action_gradient_effect: |
| case R.id.action_sepia_effect: |
| case R.id.action_negative_effect: |
| case R.id.action_pan_zoom_effect: { |
| applyEffect(item); |
| break; |
| } |
| |
| case R.id.action_no_effect: { |
| if (!item.isChecked()) { |
| final Bundle bundle = new Bundle(); |
| bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mMediaItem.getId()); |
| ((Activity) getContext()).showDialog( |
| VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID, bundle); |
| } |
| break; |
| } |
| |
| case R.id.action_rendering_mode: { |
| final Bundle bundle = new Bundle(); |
| bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mMediaItem.getId()); |
| bundle.putInt(PARAM_DIALOG_CURRENT_RENDERING_MODE, |
| mMediaItem.getAppRenderingMode()); |
| ((Activity) getContext()).showDialog( |
| VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID, bundle); |
| break; |
| } |
| |
| case R.id.action_delete_media_item: { |
| final Bundle bundle = new Bundle(); |
| bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mMediaItem.getId()); |
| ((Activity) getContext()).showDialog( |
| VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID, bundle); |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public void onDestroyActionMode(ActionMode mode) { |
| final View mediaItemView = getMediaItemView(mMediaItem.getId()); |
| if (mSelectedView != null) { |
| mLeftHandle.endMove(); |
| mRightHandle.endMove(); |
| } |
| unSelect(mediaItemView); |
| showAddMediaItemButtons(true); |
| mMediaItemActionMode = null; |
| } |
| |
| private void applyEffect(MenuItem clickedItem) { |
| if (!clickedItem.isChecked()) { |
| switch(clickedItem.getItemId()) { |
| case R.id.action_gradient_effect: |
| addEffect(EffectType.EFFECT_COLOR_GRADIENT, |
| mMediaItem.getId(), null, null); |
| clickedItem.setChecked(true); |
| break; |
| case R.id.action_sepia_effect: |
| addEffect(EffectType.EFFECT_COLOR_SEPIA, |
| mMediaItem.getId(), null, null); |
| clickedItem.setChecked(true); |
| break; |
| case R.id.action_negative_effect: |
| addEffect(EffectType.EFFECT_COLOR_NEGATIVE, |
| mMediaItem.getId(), null, null); |
| clickedItem.setChecked(true); |
| break; |
| case R.id.action_pan_zoom_effect: { |
| // Note that we don't check the pan zoom checkbox here. |
| // Because pan zoom effect will start a new activity and users |
| // could cancel applying the effect. Once pan zoom effect has |
| // really been applied. The action mode will be invalidated in |
| // onActivityResult() method and the checkbox is then checked. |
| final Intent intent = new Intent(getContext(), KenBurnsActivity.class); |
| intent.putExtra(KenBurnsActivity.PARAM_MEDIA_ITEM_ID, mMediaItem.getId()); |
| intent.putExtra(KenBurnsActivity.PARAM_FILENAME, mMediaItem.getFilename()); |
| intent.putExtra(KenBurnsActivity.PARAM_WIDTH, mMediaItem.getWidth()); |
| intent.putExtra(KenBurnsActivity.PARAM_HEIGHT, mMediaItem.getHeight()); |
| ((Activity) getContext()).startActivityForResult(intent, |
| VideoEditorActivity.REQUEST_CODE_KEN_BURNS); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| } |
| } |
| |
| /** |
| * The transition action mode handler. |
| */ |
| private class TransitionActionModeCallback implements ActionMode.Callback { |
| private final MovieTransition mTransition; |
| |
| public TransitionActionModeCallback(MovieTransition transition) { |
| mTransition = transition; |
| } |
| |
| @Override |
| public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| mTransitionActionMode = mode; |
| |
| final Activity activity = (Activity) getContext(); |
| activity.getMenuInflater().inflate(R.menu.transition_mode_menu, menu); |
| mode.setTitle(activity.getString(R.string.editor_transition_title)); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| final boolean enable = !ApiService.isProjectBeingEdited(mProject.getPath()) && |
| !mPlaybackInProgress; |
| |
| final MenuItem etmi = menu.findItem(R.id.action_change_transition); |
| etmi.setEnabled(enable); |
| |
| final MenuItem rtmi = menu.findItem(R.id.action_remove_transition); |
| rtmi.setEnabled(enable); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| switch (item.getItemId()) { |
| case R.id.action_remove_transition: { |
| final Bundle bundle = new Bundle(); |
| bundle.putString(PARAM_DIALOG_TRANSITION_ID, mTransition.getId()); |
| ((Activity) getContext()).showDialog( |
| VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID, bundle); |
| break; |
| } |
| |
| case R.id.action_change_transition: { |
| editTransition(mTransition); |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public void onDestroyActionMode(ActionMode mode) { |
| final View transitionView = getTransitionView(mTransition.getId()); |
| unSelect(transitionView); |
| showAddMediaItemButtons(true); |
| mTransitionActionMode = null; |
| } |
| } |
| |
| public MediaLinearLayout(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| |
| mMediaItemGestureListener = new ItemSimpleGestureListener() { |
| @Override |
| public boolean onSingleTapConfirmed(View view, int area, MotionEvent e) { |
| if (mPlaybackInProgress) { |
| return false; |
| } |
| |
| switch (area) { |
| case ItemSimpleGestureListener.LEFT_AREA: { |
| if (view.isSelected()) { |
| final MovieMediaItem mediaItem = (MovieMediaItem) view.getTag(); |
| final MovieMediaItem prevMediaItem = mProject.getPreviousMediaItem( |
| mediaItem.getId()); |
| pickTransition(prevMediaItem); |
| } |
| break; |
| } |
| |
| case ItemSimpleGestureListener.CENTER_AREA: { |
| break; |
| } |
| |
| case ItemSimpleGestureListener.RIGHT_AREA: { |
| if (view.isSelected()) { |
| pickTransition((MovieMediaItem) view.getTag()); |
| } |
| break; |
| } |
| } |
| select(view); |
| |
| return true; |
| } |
| |
| @Override |
| public void onLongPress(View view, MotionEvent e) { |
| if (mPlaybackInProgress) { |
| return; |
| } |
| |
| final MovieMediaItem mediaItem = (MovieMediaItem)view.getTag(); |
| if (mProject.getMediaItemCount() > 1) { |
| view.startDrag(ClipData.newPlainText("File", mediaItem.getFilename()), |
| ((MediaItemView)view).getShadowBuilder(), mediaItem.getId(), 0); |
| } |
| |
| select(view); |
| |
| if (mMediaItemActionMode == null) { |
| startActionMode(new MediaItemActionModeCallback(mediaItem)); |
| } |
| } |
| }; |
| |
| mTransitionGestureListener = new ItemSimpleGestureListener() { |
| @Override |
| public boolean onSingleTapConfirmed(View view, int area, MotionEvent e) { |
| if (mPlaybackInProgress) { |
| return false; |
| } |
| select(view); |
| return true; |
| } |
| |
| @Override |
| public void onLongPress(View view, MotionEvent e) { |
| if (mPlaybackInProgress) { |
| return; |
| } |
| |
| select(view); |
| |
| if (mTransitionActionMode == null) { |
| startActionMode(new TransitionActionModeCallback( |
| (MovieTransition) view.getTag())); |
| } |
| } |
| }; |
| |
| // Add the beginning timeline item |
| final View beginView = inflate(getContext(), R.layout.empty_left_timeline_item, null); |
| beginView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| unselectAllTimelineViews(); |
| } |
| }); |
| |
| mLeftAddClipButton = (ImageButton) beginView.findViewById( |
| R.id.add_left_media_item_button); |
| mLeftAddClipButton.setVisibility(View.GONE); |
| mLeftAddClipButton.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| if (mProject != null && mProject.getMediaItemCount() > 0) { |
| unselectAllTimelineViews(); |
| // Add a clip at the beginning of the movie. |
| mListener.onAddMediaItem(null); |
| } |
| } |
| }); |
| addView(beginView); |
| |
| // Add the end timeline item |
| final View endView = inflate(getContext(), R.layout.empty_right_timeline_item, null); |
| endView.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| unselectAllTimelineViews(); |
| } |
| }); |
| |
| mRightAddClipButton = (ImageButton) endView.findViewById( |
| R.id.add_right_media_item_button); |
| mRightAddClipButton.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View view) { |
| if (mProject != null) { |
| unselectAllTimelineViews(); |
| // Add a clip at the end of the movie. |
| final MovieMediaItem lastMediaItem = mProject.getLastMediaItem(); |
| if (lastMediaItem != null) { |
| mListener.onAddMediaItem(lastMediaItem.getId()); |
| } else { |
| mListener.onAddMediaItem(null); |
| } |
| } |
| } |
| }); |
| addView(endView); |
| |
| mLeftHandle = (HandleView)inflate(getContext(), R.layout.left_handle_view, null); |
| addView(mLeftHandle); |
| |
| mRightHandle = (HandleView)inflate(getContext(), R.layout.right_handle_view, null); |
| addView(mRightHandle); |
| |
| mHandleWidth = (int) context.getResources().getDimension(R.dimen.handle_width); |
| |
| mTransitionVerticalInset = (int) context.getResources().getDimension( |
| R.dimen.timelime_transition_vertical_inset); |
| |
| // Compute half the width of the screen (and therefore the parent view). |
| final Display display = ((Activity) context).getWindowManager().getDefaultDisplay(); |
| mHalfParentWidth = display.getWidth() / 2; |
| |
| mHandler = new Handler(); |
| |
| setMotionEventSplittingEnabled(false); |
| } |
| |
| public MediaLinearLayout(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public MediaLinearLayout(Context context) { |
| this(context, null, 0); |
| } |
| |
| public void setParentTimelineScrollView(View scrollView) { |
| mScrollView = scrollView; |
| } |
| |
| /** |
| * Called when the containing activity is resumed. |
| */ |
| public void onResume() { |
| // Invalidate all progress in case the transition generation or |
| // Ken Burns effect completed while the activity was being paused. |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object item = childView.getTag(); |
| if (item != null) { |
| if (item instanceof MovieMediaItem) { |
| ((MediaItemView) childView).resetGeneratingEffectProgress(); |
| } else if (item instanceof MovieTransition) { |
| ((TransitionView) childView).resetGeneratingTransitionProgress(); |
| } |
| } |
| } |
| } |
| |
| public void setListener(MediaLinearLayoutListener listener) { |
| mListener = listener; |
| } |
| |
| public void setProject(VideoEditorProject project) { |
| closeActionBars(); |
| clearAndHideTrimHandles(); |
| removeAllMediaItemAndTransitionViews(); |
| |
| mProject = project; |
| } |
| |
| /** |
| * @param inProgress {@code true} if playback is in progress, false otherwise |
| */ |
| public void setPlaybackInProgress(boolean inProgress) { |
| mPlaybackInProgress = inProgress; |
| setPlaybackState(inProgress); |
| // Don't allow the user to interact with media items or |
| // transitions while the playback is in progress. |
| closeActionBars(); |
| } |
| |
| /** |
| * Returns selected view's position on the timeline; -1 if none. |
| */ |
| public int getSelectedViewPos() { |
| return indexOfChild(mSelectedView); |
| } |
| |
| /** |
| * Selects the view at the specified position; null if it does not exist. |
| */ |
| public void setSelectedView(int pos) { |
| if (pos < 0) { |
| return; |
| } |
| mSelectedView = getChildAt(pos); |
| if (mSelectedView != null) { |
| select(mSelectedView); |
| } |
| } |
| |
| /** |
| * Clears existing media or transition items and adds all given media items. |
| * |
| * @param mediaItems The list of media items |
| */ |
| public void addMediaItems(List<MovieMediaItem> mediaItems) { |
| closeActionBars(); |
| removeAllMediaItemAndTransitionViews(); |
| |
| for (MovieMediaItem mediaItem : mediaItems) { |
| addMediaItem(mediaItem); |
| } |
| } |
| |
| /** |
| * Adds a new media item at the end of the timeline. |
| * |
| * @param mediaItem The media item |
| */ |
| private void addMediaItem(MovieMediaItem mediaItem) { |
| final View mediaItemView = inflate(getContext(), R.layout.media_item, null); |
| ((MediaItemView) mediaItemView).setGestureListener(mMediaItemGestureListener); |
| ((MediaItemView) mediaItemView).setProjectPath(mProject.getPath()); |
| mediaItemView.setTag(mediaItem); |
| |
| // Add the new view |
| final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( |
| LinearLayout.LayoutParams.WRAP_CONTENT, |
| LinearLayout.LayoutParams.FILL_PARENT); |
| // Add the view before the end view, left handle and right handle views. |
| addView(mediaItemView, getChildCount() - 3, lp); |
| |
| // If the new media item has beginning and end transitions, add them. |
| final MovieTransition beginTransition = mediaItem.getBeginTransition(); |
| if (beginTransition != null) { |
| final int cc = getChildCount(); |
| // Account for the beginning and end views and the trim handles |
| if (cc > 5) { // There is a previous view (transition or media item) |
| final View view = getChildAt(cc - 5); |
| final Object tag = view.getTag(); |
| // Do not add transition if it already exists |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem prevMediaItem = (MovieMediaItem)tag; |
| addTransition(beginTransition, prevMediaItem.getId()); |
| } |
| } else { // This is the first media item |
| addTransition(beginTransition, null); |
| } |
| } |
| |
| final MovieTransition endTransition = mediaItem.getEndTransition(); |
| if (endTransition != null) { |
| addTransition(endTransition, mediaItem.getId()); |
| } |
| |
| requestLayout(); |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| |
| // Now we can add clips by tapping the beginning view |
| mLeftAddClipButton.setVisibility(View.VISIBLE); |
| } |
| |
| /** |
| * Inserts a new media item after the specified media item id. |
| * |
| * @param mediaItem The media item |
| * @param afterMediaItemId The id of the media item preceding the media item |
| */ |
| public void insertMediaItem(MovieMediaItem mediaItem, String afterMediaItemId) { |
| final View mediaItemView = inflate(getContext(), R.layout.media_item, null); |
| ((MediaItemView)mediaItemView).setGestureListener(mMediaItemGestureListener); |
| ((MediaItemView)mediaItemView).setProjectPath(mProject.getPath()); |
| |
| mediaItemView.setTag(mediaItem); |
| |
| int insertViewIndex; |
| if (afterMediaItemId != null) { |
| if ((insertViewIndex = getMediaItemViewIndex(afterMediaItemId)) == -1) { |
| Log.e(TAG, "Media item not found: " + afterMediaItemId); |
| return; |
| } |
| |
| insertViewIndex++; |
| |
| if (insertViewIndex < getChildCount()) { |
| final Object tag = getChildAt(insertViewIndex).getTag(); |
| if (tag != null && tag instanceof MovieTransition) { |
| // Remove the transition following the media item |
| removeViewAt(insertViewIndex); |
| } |
| } |
| } else { // Insert at the beginning |
| // If we have a transition at the beginning remove it |
| final Object tag = getChildAt(1).getTag(); |
| if (tag != null && tag instanceof MovieTransition) { |
| removeViewAt(1); |
| } |
| |
| insertViewIndex = 1; |
| } |
| |
| // Add the new view |
| final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( |
| LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT); |
| addView(mediaItemView, insertViewIndex, lp); |
| |
| // If the new media item has beginning and end transitions add them |
| final MovieTransition beginTransition = mediaItem.getBeginTransition(); |
| if (beginTransition != null) { |
| if (insertViewIndex > 1) { // There is a previous view (transition or media item) |
| final View view = getChildAt(insertViewIndex - 1); |
| final Object tag = view.getTag(); |
| // Do not add transition if it already exists |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem prevMediaItem = (MovieMediaItem)tag; |
| addTransition(beginTransition, prevMediaItem.getId()); |
| } |
| } else { // This is the first media item |
| addTransition(beginTransition, null); |
| } |
| } |
| |
| final MovieTransition endTransition = mediaItem.getEndTransition(); |
| if (endTransition != null) { |
| addTransition(endTransition, mediaItem.getId()); |
| } |
| |
| requestLayout(); |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| |
| // Now we can add clips by tapping the beginning view |
| mLeftAddClipButton.setVisibility(View.VISIBLE); |
| } |
| |
| /** |
| * Updates the specified media item. |
| * |
| * @param mediaItem The media item to be updated |
| */ |
| public void updateMediaItem(MovieMediaItem mediaItem) { |
| final String mediaItemId = mediaItem.getId(); |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object tag = childView.getTag(); |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem mi = (MovieMediaItem) tag; |
| if (mediaItemId.equals(mi.getId())) { |
| if (mediaItem != mi) { |
| // The media item is a new instance of the media item |
| childView.setTag(mediaItem); |
| if (mediaItem.getBeginTransition() != null) { |
| if (i > 0) { |
| final View tView = getChildAt(i - 1); |
| final Object tagT = tView.getTag(); |
| if (tagT != null && tagT instanceof MovieTransition) { |
| tView.setTag(mediaItem.getBeginTransition()); |
| } |
| } |
| } |
| |
| if (mediaItem.getEndTransition() != null) { |
| if (i < childrenCount - 1) { |
| final View tView = getChildAt(i + 1); |
| final Object tagT = tView.getTag(); |
| if (tagT != null && tagT instanceof MovieTransition) { |
| tView.setTag(mediaItem.getEndTransition()); |
| } |
| } |
| } |
| } |
| |
| if (childView.isSelected()) { |
| mLeftHandle.setEnabled(true); |
| mRightHandle.setEnabled(true); |
| } |
| |
| break; |
| } |
| } |
| } |
| |
| requestLayout(); |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| } |
| |
| /** |
| * Removes a media item view. |
| * |
| * @param mediaItemId The media item id |
| * @param transition The transition inserted at the removal position |
| * if a theme is in use. |
| * |
| * @return The view which was removed |
| */ |
| public View removeMediaItem(String mediaItemId, MovieTransition transition) { |
| final int childrenCount = getChildCount(); |
| MovieMediaItem prevMediaItem = null; |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object tag = childView.getTag(); |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem mi = (MovieMediaItem)tag; |
| if (mediaItemId.equals(mi.getId())) { |
| int mediaItemViewIndex = i; |
| |
| // Remove the before transition |
| if (mediaItemViewIndex > 0) { |
| final Object beforeTag = getChildAt(mediaItemViewIndex - 1).getTag(); |
| if (beforeTag != null && beforeTag instanceof MovieTransition) { |
| // Remove the transition view |
| removeViewAt(mediaItemViewIndex - 1); |
| mediaItemViewIndex--; |
| } |
| } |
| |
| // Remove the after transition view |
| if (mediaItemViewIndex < getChildCount() - 1) { |
| final Object afterTag = getChildAt(mediaItemViewIndex + 1).getTag(); |
| if (afterTag != null && afterTag instanceof MovieTransition) { |
| // Remove the transition view |
| removeViewAt(mediaItemViewIndex + 1); |
| } |
| } |
| |
| // Remove the media item view |
| removeViewAt(mediaItemViewIndex); |
| |
| if (transition != null) { |
| addTransition(transition, |
| prevMediaItem != null ? prevMediaItem.getId() : null); |
| } |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| |
| if (mProject.getMediaItemCount() == 0) { |
| // We cannot add clips by tapping the beginning view |
| mLeftAddClipButton.setVisibility(View.GONE); |
| } |
| return childView; |
| } |
| |
| prevMediaItem = mi; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Creates a new transition. |
| * |
| * @param afterMediaItemId Insert the transition after this media item id |
| * @param transitionType The transition type |
| * @param transitionDurationMs The transition duration in ms |
| */ |
| public void addTransition(String afterMediaItemId, int transitionType, |
| long transitionDurationMs) { |
| unselectAllTimelineViews(); |
| |
| final MovieMediaItem afterMediaItem; |
| if (afterMediaItemId != null) { |
| afterMediaItem = mProject.getMediaItem(afterMediaItemId); |
| if (afterMediaItem == null) { |
| return; |
| } |
| } else { |
| afterMediaItem = null; |
| } |
| |
| final String id = ApiService.generateId(); |
| switch (transitionType) { |
| case TransitionType.TRANSITION_TYPE_ALPHA_CONTOUR: { |
| ApiService.insertAlphaTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, |
| R.raw.mask_contour, 50, false); |
| break; |
| } |
| |
| case TransitionType.TRANSITION_TYPE_ALPHA_DIAGONAL: { |
| ApiService.insertAlphaTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, |
| R.raw.mask_diagonal, 50, false); |
| break; |
| } |
| |
| case TransitionType.TRANSITION_TYPE_CROSSFADE: { |
| ApiService.insertCrossfadeTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR); |
| break; |
| } |
| |
| case TransitionType.TRANSITION_TYPE_FADE_BLACK: { |
| ApiService.insertFadeBlackTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR); |
| break; |
| } |
| |
| case TransitionType.TRANSITION_TYPE_SLIDING_RIGHT_OUT_LEFT_IN: { |
| ApiService.insertSlidingTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, |
| TransitionSliding.DIRECTION_RIGHT_OUT_LEFT_IN); |
| break; |
| } |
| |
| case TransitionType.TRANSITION_TYPE_SLIDING_LEFT_OUT_RIGHT_IN: { |
| ApiService.insertSlidingTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, |
| TransitionSliding.DIRECTION_LEFT_OUT_RIGHT_IN); |
| break; |
| } |
| |
| case TransitionType.TRANSITION_TYPE_SLIDING_TOP_OUT_BOTTOM_IN: { |
| ApiService.insertSlidingTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, |
| TransitionSliding.DIRECTION_TOP_OUT_BOTTOM_IN); |
| break; |
| } |
| |
| case TransitionType.TRANSITION_TYPE_SLIDING_BOTTOM_OUT_TOP_IN: { |
| ApiService.insertSlidingTransition(getContext(), mProject.getPath(), |
| afterMediaItemId, id, transitionDurationMs, Transition.BEHAVIOR_LINEAR, |
| TransitionSliding.DIRECTION_BOTTOM_OUT_TOP_IN); |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| } |
| |
| /** |
| * Edits a transition. |
| * |
| * @param afterMediaItemId Insert the transition after this media item id |
| * @param transitionId The transition id |
| * @param transitionType The transition type |
| * @param transitionDurationMs The transition duration in ms |
| */ |
| public void editTransition(String afterMediaItemId, String transitionId, int transitionType, |
| long transitionDurationMs) { |
| final MovieTransition transition = mProject.getTransition(transitionId); |
| if (transition == null) { |
| return; |
| } |
| |
| // Check if the type or duration had changed |
| if (transition.getType() != transitionType) { |
| // Remove the transition and add it again |
| ApiService.removeTransition(getContext(), mProject.getPath(), transitionId); |
| addTransition(afterMediaItemId, transitionType, transitionDurationMs); |
| } else if (transition.getAppDuration() != transitionDurationMs) { |
| transition.setAppDuration(transitionDurationMs); |
| ApiService.setTransitionDuration(getContext(), mProject.getPath(), transitionId, |
| transitionDurationMs); |
| } |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| } |
| |
| /** |
| * Adds a new transition after the specified media id. This method assumes that a |
| * transition does not exist at the insertion point. |
| * |
| * @param transition The transition to be added |
| * @param afterMediaItemId After the specified media item id |
| * |
| * @return The transition view that was added, {@code null} upon errors. |
| */ |
| public View addTransition(MovieTransition transition, String afterMediaItemId) { |
| // Determine the insert position |
| int index; |
| if (afterMediaItemId != null) { |
| index = -1; |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final Object tag = getChildAt(i).getTag(); |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem mi = (MovieMediaItem) tag; |
| if (afterMediaItemId.equals(mi.getId())) { |
| index = i + 1; |
| break; |
| } |
| } |
| } |
| |
| if (index < 0) { |
| Log.e(TAG, "addTransition media item not found: " + afterMediaItemId); |
| return null; |
| } |
| } else { |
| index = 1; |
| } |
| |
| final View transitionView = inflate(getContext(), R.layout.transition_view, null); |
| ((TransitionView) transitionView).setGestureListener(mTransitionGestureListener); |
| ((TransitionView) transitionView).setProjectPath(mProject.getPath()); |
| transitionView.setTag(transition); |
| |
| final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( |
| LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.FILL_PARENT); |
| addView(transitionView, index, lp); |
| |
| // Adjust the size of all the views |
| requestLayout(); |
| |
| // If this transition was added by the user invalidate the menu item |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| |
| return transitionView; |
| } |
| |
| /** |
| * Updates a transition. |
| * |
| * @param transitionId The transition id |
| */ |
| public void updateTransition(String transitionId) { |
| requestLayout(); |
| invalidate(); |
| } |
| |
| /** |
| * Removes a transition with the specified id. |
| * |
| * @param transitionId The transition id |
| */ |
| public void removeTransition(String transitionId) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final Object tag = getChildAt(i).getTag(); |
| if (tag != null && tag instanceof MovieTransition) { |
| final MovieTransition transition = (MovieTransition)tag; |
| if (transitionId.equals(transition.getId())) { |
| // Remove the view |
| removeViewAt(i); |
| |
| // Adjust the size of all the views |
| requestLayout(); |
| |
| // If this transition was removed by the user invalidate the menu item |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| |
| return; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Invalidates the available action modes. Used to refresh menu contents. |
| */ |
| public void invalidateActionBar() { |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| if (mTransitionActionMode != null) { |
| mTransitionActionMode.invalidate(); |
| } |
| } |
| |
| /** |
| * A Ken Burns movie is encoded for an MediaImageItem. |
| * |
| * @param mediaItemId The media item id |
| * @param action The action |
| * @param progress Progress value (between 0..100) |
| */ |
| public void onGeneratePreviewMediaItemProgress(String mediaItemId, int action, int progress) { |
| // Display the progress while generating the Ken Burns video clip |
| final MediaItemView view = (MediaItemView) getMediaItemView(mediaItemId); |
| if (view != null) { |
| view.setGeneratingEffectProgress(progress); |
| |
| if (view.isSelected()) { |
| if (progress == 0) { |
| mLeftHandle.setEnabled(false); |
| mRightHandle.setEnabled(false); |
| } else if (progress == 100) { |
| mLeftHandle.setEnabled(true); |
| mRightHandle.setEnabled(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * A transition is being encoded. |
| * |
| * @param transitionId The transition id |
| * @param action The action |
| * @param progress The progress |
| */ |
| public void onGeneratePreviewTransitionProgress(String transitionId, int action, |
| int progress) { |
| // Display the progress while generating the transition |
| final TransitionView view = (TransitionView) getTransitionView(transitionId); |
| if (view != null) { |
| view.setGeneratingTransitionProgress(progress); |
| |
| if (view.isSelected()) { |
| if (progress == 0) { |
| mLeftHandle.setEnabled(false); |
| mRightHandle.setEnabled(false); |
| } else if (progress == 100) { |
| mLeftHandle.setEnabled(true); |
| mRightHandle.setEnabled(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Creates a new effect on the specified media item. |
| * |
| * @param effectType The effect type |
| * @param mediaItemId Add the effect for this media item id |
| * @param startRect The start rectangle |
| * @param endRect The end rectangle |
| */ |
| public void addEffect(int effectType, String mediaItemId, Rect startRect, Rect endRect) { |
| final MovieMediaItem mediaItem = mProject.getMediaItem(mediaItemId); |
| if (mediaItem == null) { |
| Log.e(TAG, "addEffect media item not found: " + mediaItemId); |
| return; |
| } |
| |
| final String id = ApiService.generateId(); |
| switch (effectType) { |
| case EffectType.EFFECT_KEN_BURNS: { |
| ApiService.addEffectKenBurns(getContext(), mProject.getPath(), mediaItemId, |
| id, 0, mediaItem.getDuration(), startRect, endRect); |
| break; |
| } |
| |
| case EffectType.EFFECT_COLOR_GRADIENT: { |
| ApiService.addEffectColor(getContext(), mProject.getPath(), mediaItemId, id, 0, |
| mediaItem.getDuration(), EffectColor.TYPE_GRADIENT, |
| EffectColor.GRAY); |
| break; |
| } |
| |
| case EffectType.EFFECT_COLOR_SEPIA: { |
| ApiService.addEffectColor(getContext(), mProject.getPath(), mediaItemId, id, 0, |
| mediaItem.getDuration(), EffectColor.TYPE_SEPIA, 0); |
| break; |
| } |
| |
| case EffectType.EFFECT_COLOR_NEGATIVE: { |
| ApiService.addEffectColor(getContext(), mProject.getPath(), mediaItemId, id, 0, |
| mediaItem.getDuration(), EffectColor.TYPE_NEGATIVE, 0); |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| } |
| |
| /** |
| * Set the media item thumbnail. |
| * |
| * @param mediaItemId The media item id |
| * @param bitmap The bitmap |
| * @param index The index of the bitmap |
| * @param token The token given in the original request |
| * |
| * @return true if the bitmap is used |
| */ |
| public boolean setMediaItemThumbnail( |
| String mediaItemId, Bitmap bitmap, int index, int token) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final Object tag = getChildAt(i).getTag(); |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem mi = (MovieMediaItem)tag; |
| if (mediaItemId.equals(mi.getId())) { |
| return ((MediaItemView)getChildAt(i)).setBitmap( |
| bitmap, index, token); |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Sets the transition thumbnails. |
| * |
| * @param transitionId The transition id |
| * @param bitmaps The bitmaps array |
| * |
| * @return true if the bitmaps were used |
| */ |
| public boolean setTransitionThumbnails(String transitionId, Bitmap[] bitmaps) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final Object tag = getChildAt(i).getTag(); |
| if (tag != null && tag instanceof MovieTransition) { |
| final MovieTransition transition = (MovieTransition)tag; |
| if (transitionId.equals(transition.getId())) { |
| return ((TransitionView)getChildAt(i)).setBitmaps(bitmaps); |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| // Compute the total duration of the project. |
| final long totalDurationMs = mProject.computeDuration(); |
| |
| // Total available width for putting media items and transitions. |
| // We subtract 2 half screen widths from the width because we put |
| // 2 empty view at the beginning and end of the timeline, each with |
| // half screen width. We then layout each child view into the |
| // available width. |
| final int viewWidth = getWidth() - (2 * mHalfParentWidth); |
| |
| // If we are in trimming mode, the left view width might be different |
| // due to trimming; otherwise it equals half of screen width. |
| final int leftViewWidth = (mSelectedView != null) ? |
| (Integer) mScrollView.getTag(R.id.left_view_width) : mHalfParentWidth; |
| |
| // Top and bottom position are fixed for media item views. For transition views, |
| // there is additional inset which makes them smaller. See below. |
| final int top = getPaddingTop(); |
| final int bottom = b - t; |
| |
| long startMs = 0; |
| int left = 0; |
| |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View view = getChildAt(i); |
| final Object tag = view.getTag(); |
| if (tag != null) { |
| final long durationMs = computeViewDuration(view); |
| |
| final int right = (int)((float)((startMs + durationMs) * viewWidth) / |
| (float)totalDurationMs) + leftViewWidth; |
| |
| if (tag instanceof MovieMediaItem) { |
| if (left != view.getLeft() || right != view.getRight()) { |
| final int oldLeft = view.getLeft(); |
| final int oldRight = view.getRight(); |
| view.layout(left, top, right, bottom); |
| ((MediaItemView) view).onLayoutPerformed(oldLeft, oldRight); |
| } else { |
| view.layout(left, top, right, bottom); |
| } |
| } else { // Transition view. |
| // Note that we set additional inset so it looks smaller |
| // than media item views on the timeline. |
| view.layout(left, |
| top + mTransitionVerticalInset, |
| right, |
| bottom - mTransitionVerticalInset); |
| } |
| |
| startMs += durationMs; |
| left = right; |
| } else if (view == mLeftHandle && mSelectedView != null) { |
| // We are in trimming mode, the left handle must be shown. |
| view.layout(mSelectedView.getLeft() - mHandleWidth, |
| top + mSelectedView.getPaddingTop(), |
| mSelectedView.getLeft(), |
| bottom - mSelectedView.getPaddingBottom()); |
| } else if (view == mRightHandle && mSelectedView != null) { |
| // We are in trimming mode, the right handle must be shown. |
| view.layout(mSelectedView.getRight(), |
| top + mSelectedView.getPaddingTop(), |
| mSelectedView.getRight() + mHandleWidth, |
| bottom - mSelectedView.getPaddingBottom()); |
| } else if (i == 0) { // Begin view |
| view.layout(0, top, leftViewWidth, bottom); |
| left += leftViewWidth; |
| } else { // End view |
| view.layout(left, top, getWidth(), bottom); |
| } |
| } |
| mMoveLayoutPending = false; |
| } |
| |
| /** |
| * Computes the duration of the specified view. |
| * |
| * @param view The specified view |
| * |
| * @return The duration in milliseconds, 0 if the specified view is not a media item view |
| * or a transition view |
| */ |
| private long computeViewDuration(View view) { |
| long durationMs; |
| final Object tag = view.getTag(); |
| if (tag != null) { |
| if (tag instanceof MovieMediaItem) { |
| final MovieMediaItem mediaItem = (MovieMediaItem) view.getTag(); |
| durationMs = mediaItem.getAppTimelineDuration(); |
| if (mediaItem.getBeginTransition() != null) { |
| durationMs -= mediaItem.getBeginTransition().getAppDuration(); |
| } |
| |
| if (mediaItem.getEndTransition() != null) { |
| durationMs -= mediaItem.getEndTransition().getAppDuration(); |
| } |
| } else { // Transition |
| final MovieTransition transition = (MovieTransition) tag; |
| durationMs = transition.getAppDuration(); |
| } |
| } else { |
| durationMs = 0; |
| } |
| |
| return durationMs; |
| } |
| |
| /** |
| * Creates a new dialog. |
| * |
| * @param id The dialog id |
| * @param bundle The dialog bundle |
| * |
| * @return The dialog |
| */ |
| public Dialog onCreateDialog(int id, final Bundle bundle) { |
| // If the project is not yet loaded do nothing. |
| if (mProject == null) { |
| return null; |
| } |
| |
| switch (id) { |
| case VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID: { |
| final MovieMediaItem mediaItem = mProject.getMediaItem( |
| bundle.getString(PARAM_DIALOG_MEDIA_ITEM_ID)); |
| if (mediaItem == null) { |
| return null; |
| } |
| |
| final Activity activity = (Activity) getContext(); |
| return AlertDialogs.createAlert(activity, |
| FileUtils.getSimpleName(mediaItem.getFilename()), |
| 0, mediaItem.isVideoClip() ? |
| activity.getString(R.string.editor_remove_video_question) : |
| activity.getString(R.string.editor_remove_image_question), |
| activity.getString(R.string.yes), |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.finish(); |
| mMediaItemActionMode = null; |
| } |
| unselectAllTimelineViews(); |
| |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID); |
| |
| ApiService.removeMediaItem(activity, mProject.getPath(), mediaItem.getId(), |
| mProject.getTheme()); |
| } |
| }, activity.getString(R.string.no), new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID); |
| } |
| }, new DialogInterface.OnCancelListener() { |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_MEDIA_ITEM_ID); |
| } |
| }, true); |
| } |
| |
| case VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID: { |
| final MovieMediaItem mediaItem = mProject.getMediaItem( |
| bundle.getString(PARAM_DIALOG_MEDIA_ITEM_ID)); |
| if (mediaItem == null) { |
| return null; |
| } |
| |
| final Activity activity = (Activity)getContext(); |
| final AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); |
| builder.setTitle(activity.getString(R.string.editor_change_rendering_mode)); |
| final CharSequence[] renderingModeStrings = new CharSequence[3]; |
| renderingModeStrings[0] = getContext().getString(R.string.rendering_mode_black_borders); |
| renderingModeStrings[1] = getContext().getString(R.string.rendering_mode_stretch); |
| renderingModeStrings[2] = getContext().getString(R.string.rendering_mode_crop); |
| |
| final int currentRenderingMode = bundle.getInt(PARAM_DIALOG_CURRENT_RENDERING_MODE); |
| final int currentRenderingModeIndex; |
| switch (currentRenderingMode) { |
| case MediaItem.RENDERING_MODE_CROPPING: { |
| currentRenderingModeIndex = 2; |
| break; |
| } |
| |
| case MediaItem.RENDERING_MODE_STRETCH: { |
| currentRenderingModeIndex = 1; |
| break; |
| } |
| |
| case MediaItem.RENDERING_MODE_BLACK_BORDER: |
| default: { |
| currentRenderingModeIndex = 0; |
| break; |
| } |
| } |
| |
| builder.setSingleChoiceItems(renderingModeStrings, currentRenderingModeIndex, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| switch (which) { |
| case 0: { |
| mediaItem.setAppRenderingMode(MediaItem.RENDERING_MODE_BLACK_BORDER); |
| ApiService.setMediaItemRenderingMode(getContext(), |
| mProject.getPath(), mediaItem.getId(), |
| MediaItem.RENDERING_MODE_BLACK_BORDER); |
| break; |
| } |
| |
| case 1: { |
| mediaItem.setAppRenderingMode(MediaItem.RENDERING_MODE_STRETCH); |
| ApiService.setMediaItemRenderingMode(getContext(), |
| mProject.getPath(), |
| mediaItem.getId(), MediaItem.RENDERING_MODE_STRETCH); |
| break; |
| } |
| |
| case 2: { |
| mediaItem.setAppRenderingMode(MediaItem.RENDERING_MODE_CROPPING); |
| ApiService.setMediaItemRenderingMode(getContext(), |
| mProject.getPath(), |
| mediaItem.getId(), MediaItem.RENDERING_MODE_CROPPING); |
| break; |
| } |
| |
| default: { |
| break; |
| } |
| } |
| activity.removeDialog(VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID); |
| } |
| }); |
| builder.setCancelable(true); |
| builder.setOnCancelListener(new DialogInterface.OnCancelListener() { |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_CHANGE_RENDERING_MODE_ID); |
| } |
| }); |
| return builder.create(); |
| } |
| |
| case VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID: { |
| final MovieTransition transition = mProject.getTransition( |
| bundle.getString(PARAM_DIALOG_TRANSITION_ID)); |
| if (transition == null) { |
| return null; |
| } |
| |
| final Activity activity = (Activity) getContext(); |
| return AlertDialogs.createAlert(activity, |
| activity.getString(R.string.remove), |
| 0, activity.getString(R.string.editor_remove_transition_question), |
| activity.getString(R.string.yes), |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| if (mTransitionActionMode != null) { |
| mTransitionActionMode.finish(); |
| mTransitionActionMode = null; |
| } |
| unselectAllTimelineViews(); |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID); |
| |
| ApiService.removeTransition(activity, mProject.getPath(), |
| transition.getId()); |
| } |
| }, activity.getString(R.string.no), new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID); |
| } |
| }, new DialogInterface.OnCancelListener() { |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_TRANSITION_ID); |
| } |
| }, true); |
| } |
| |
| case VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID: { |
| final MovieMediaItem mediaItem = mProject.getMediaItem( |
| bundle.getString(PARAM_DIALOG_MEDIA_ITEM_ID)); |
| if (mediaItem == null) { |
| return null; |
| } |
| |
| final Activity activity = (Activity) getContext(); |
| return AlertDialogs.createAlert(activity, |
| FileUtils.getSimpleName(mediaItem.getFilename()), |
| 0, activity.getString(R.string.editor_remove_effect_question), |
| activity.getString(R.string.yes), |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID); |
| |
| ApiService.removeEffect(activity, mProject.getPath(), |
| mediaItem.getId(), mediaItem.getEffect().getId()); |
| |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| } |
| }, activity.getString(R.string.no), new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID); |
| } |
| }, new DialogInterface.OnCancelListener() { |
| @Override |
| public void onCancel(DialogInterface dialog) { |
| activity.removeDialog(VideoEditorActivity.DIALOG_REMOVE_EFFECT_ID); |
| } |
| }, true); |
| } |
| |
| default: { |
| return null; |
| } |
| } |
| } |
| |
| @Override |
| public boolean onDragEvent(DragEvent event) { |
| boolean result = false; |
| switch (event.getAction()) { |
| case DragEvent.ACTION_DRAG_STARTED: { |
| // Claim to accept any dragged content |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "ACTION_DRAG_STARTED: " + event); |
| } |
| |
| mDragMediaItemId = (String)event.getLocalState(); |
| |
| // Hide the handles while dragging |
| mLeftHandle.setVisibility(View.GONE); |
| mRightHandle.setVisibility(View.GONE); |
| |
| mDropAfterMediaItem = null; |
| mDropIndex = -1; |
| |
| mFirstEntered = true; |
| // This view accepts drag |
| result = true; |
| break; |
| } |
| |
| case DragEvent.ACTION_DRAG_ENTERED: { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "ACTION_DRAG_ENTERED: " + event); |
| } |
| |
| if (!mFirstEntered && mDropIndex >= 0) { |
| mScrollView.setTag(R.id.playhead_type, |
| TimelineHorizontalScrollView.PLAYHEAD_MOVE_OK); |
| } else { |
| mScrollView.setTag(R.id.playhead_type, |
| TimelineHorizontalScrollView.PLAYHEAD_MOVE_NOT_OK); |
| } |
| mScrollView.invalidate(); |
| |
| mFirstEntered = false; |
| break; |
| } |
| |
| case DragEvent.ACTION_DRAG_EXITED: { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "ACTION_DRAG_EXITED: " + event); |
| } |
| |
| // Redraw the "normal playhead" |
| mScrollView.setTag(R.id.playhead_type, TimelineHorizontalScrollView.PLAYHEAD_NORMAL); |
| mScrollView.invalidate(); |
| break; |
| } |
| |
| case DragEvent.ACTION_DRAG_ENDED: { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "ACTION_DRAG_ENDED: " + event); |
| } |
| |
| mDragMediaItemId = null; |
| mDropIndex = -1; |
| |
| // Hide the handles while dragging |
| mLeftHandle.setVisibility(View.VISIBLE); |
| mRightHandle.setVisibility(View.VISIBLE); |
| |
| // Redraw the "normal playhead" |
| mScrollView.setTag(R.id.playhead_type, TimelineHorizontalScrollView.PLAYHEAD_NORMAL); |
| mScrollView.invalidate(); |
| |
| requestLayout(); |
| break; |
| } |
| |
| case DragEvent.ACTION_DRAG_LOCATION: { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "ACTION_DRAG_LOCATION: " + event); |
| } |
| |
| moveToPosition(event.getX()); |
| // We returned true to DRAG_STARTED, so return true here |
| result = true; |
| break; |
| } |
| |
| case DragEvent.ACTION_DROP: { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "ACTION_DROP: " + event); |
| } |
| |
| if (mDropIndex >= 0) { |
| final String afterMediaItemId = |
| mDropAfterMediaItem != null ? mDropAfterMediaItem.getId() : null; |
| if (Log.isLoggable(TAG, Log.DEBUG)) { |
| Log.d(TAG, "ACTION_DROP: Index: " + mDropIndex + " | " + afterMediaItemId); |
| } |
| ApiService.moveMediaItem(getContext(), mProject.getPath(), mDragMediaItemId, |
| afterMediaItemId, null); |
| } |
| result = true; |
| break; |
| } |
| |
| |
| default: { |
| if (Log.isLoggable(TAG, Log.VERBOSE)) { |
| Log.v(TAG, "Other drag event: " + event); |
| } |
| result = true; |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Move the playhead during a move operation |
| * |
| * @param eventX The event horizontal position |
| */ |
| private void moveToPosition(float eventX) { |
| final int x = (int)eventX - mScrollView.getScrollX(); |
| final long now = System.currentTimeMillis(); |
| if (now - mPrevDragScrollTime > 300) { |
| if (x < mPrevDragPosition - 42) { // Backwards |
| final long positionMs = getLeftDropPosition(); |
| if (mDropIndex >= 0) { |
| // Redraw the "move ok playhead" |
| mScrollView.setTag(R.id.playhead_type, |
| TimelineHorizontalScrollView.PLAYHEAD_MOVE_OK); |
| } else { |
| // Redraw the "move not ok playhead" |
| mScrollView.setTag(R.id.playhead_type, |
| TimelineHorizontalScrollView.PLAYHEAD_MOVE_NOT_OK); |
| } |
| |
| mListener.onRequestMovePlayhead(positionMs, true); |
| mScrollView.invalidate(); |
| |
| mPrevDragPosition = x; |
| mPrevDragScrollTime = now; |
| } else if (x > mPrevDragPosition + 42) { // Forward |
| final long positionMs = getRightDropPosition(); |
| if (mDropIndex >= 0) { |
| // Redraw the "move ok playhead" |
| mScrollView.setTag(R.id.playhead_type, |
| TimelineHorizontalScrollView.PLAYHEAD_MOVE_OK); |
| } else { |
| // Redraw the "move not ok playhead" |
| mScrollView.setTag(R.id.playhead_type, |
| TimelineHorizontalScrollView.PLAYHEAD_MOVE_NOT_OK); |
| } |
| |
| mListener.onRequestMovePlayhead(positionMs, true); |
| mScrollView.invalidate(); |
| |
| mPrevDragPosition = x; |
| mPrevDragScrollTime = now; |
| } |
| } else { |
| mPrevDragPosition = x; |
| } |
| } |
| |
| // Returns the begin time of a media item (exclude transition). |
| private long getBeginTime(MovieMediaItem item) { |
| final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); |
| long beginMs = 0; |
| final int mediaItemsCount = mediaItems.size(); |
| for (int i = 0; i < mediaItemsCount; i++) { |
| final MovieMediaItem mediaItem = mediaItems.get(i); |
| final MovieTransition beginTransition = mediaItem.getBeginTransition(); |
| final MovieTransition endTransition = mediaItem.getEndTransition(); |
| |
| if (item.getId().equals(mediaItem.getId())) { |
| if (beginTransition != null) { |
| beginMs += beginTransition.getAppDuration(); |
| } |
| return beginMs; |
| } |
| |
| beginMs += mediaItem.getAppTimelineDuration(); |
| |
| if (endTransition != null) { |
| beginMs -= endTransition.getAppDuration(); |
| } |
| } |
| |
| return 0; |
| } |
| |
| // Returns the end time of a media item (exclude transition) |
| private long getEndTime(MovieMediaItem item) { |
| final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); |
| long endMs = 0; |
| final int mediaItemsCount = mediaItems.size(); |
| for (int i = 0; i < mediaItemsCount; i++) { |
| final MovieMediaItem mediaItem = mediaItems.get(i); |
| final MovieTransition beginTransition = mediaItem.getBeginTransition(); |
| final MovieTransition endTransition = mediaItem.getEndTransition(); |
| |
| endMs += mediaItem.getAppTimelineDuration(); |
| |
| if (endTransition != null) { |
| endMs -= endTransition.getAppDuration(); |
| } |
| |
| if (item.getId().equals(mediaItem.getId())) { |
| return endMs; |
| } |
| |
| } |
| |
| return 0; |
| } |
| |
| /** |
| * @return The valid time location of the drop (-1 if none) |
| */ |
| private long getLeftDropPosition() { |
| final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); |
| long beginMs = 0; |
| long endMs = 0; |
| long timeMs = mProject.getPlayheadPos(); |
| |
| final int mediaItemsCount = mediaItems.size(); |
| for (int i = 0; i < mediaItemsCount; i++) { |
| final MovieMediaItem mediaItem = mediaItems.get(i); |
| |
| endMs = beginMs + mediaItem.getAppTimelineDuration(); |
| |
| if (mediaItem.getEndTransition() != null) { |
| if (i < mediaItemsCount - 1) { |
| endMs -= mediaItem.getEndTransition().getAppDuration(); |
| } |
| } |
| |
| if (timeMs > beginMs && timeMs <= endMs) { |
| if (mediaItem.getBeginTransition() != null) { |
| beginMs += mediaItem.getBeginTransition().getAppDuration(); |
| } |
| |
| if (!mDragMediaItemId.equals(mediaItem.getId())) { |
| if (i > 0) { |
| // Check if the previous item is the drag item |
| final MovieMediaItem prevMediaItem = mediaItems.get(i - 1); |
| if (!mDragMediaItemId.equals(prevMediaItem.getId())) { |
| mDropAfterMediaItem = prevMediaItem; |
| mDropIndex = i; |
| return beginMs; |
| } else { |
| mDropAfterMediaItem = null; |
| mDropIndex = -1; |
| return beginMs; |
| } |
| } else { |
| mDropAfterMediaItem = null; |
| mDropIndex = 0; |
| return 0; |
| } |
| } else { |
| mDropAfterMediaItem = null; |
| mDropIndex = -1; |
| return beginMs; |
| } |
| } |
| |
| beginMs = endMs; |
| } |
| |
| return timeMs; |
| } |
| |
| /** |
| * @return The valid time location of the drop (-1 if none) |
| */ |
| private long getRightDropPosition() { |
| final List<MovieMediaItem> mediaItems = mProject.getMediaItems(); |
| long beginMs = 0; |
| long endMs = 0; |
| long timeMs = mProject.getPlayheadPos(); |
| |
| final int mediaItemsCount = mediaItems.size(); |
| for (int i = 0; i < mediaItemsCount; i++) { |
| final MovieMediaItem mediaItem = mediaItems.get(i); |
| |
| endMs = beginMs + mediaItem.getAppTimelineDuration(); |
| |
| if (mediaItem.getEndTransition() != null) { |
| if (i < mediaItemsCount - 1) { |
| endMs -= mediaItem.getEndTransition().getAppDuration(); |
| } |
| } |
| |
| if (timeMs >= beginMs && timeMs < endMs) { |
| if (!mDragMediaItemId.equals(mediaItem.getId())) { |
| if (i < mediaItemsCount - 1) { |
| // Check if the next item is the drag item |
| final MovieMediaItem nextMediaItem = mediaItems.get(i + 1); |
| if (!mDragMediaItemId.equals(nextMediaItem.getId())) { |
| mDropAfterMediaItem = mediaItem; |
| mDropIndex = i; |
| return endMs; |
| } else { |
| mDropAfterMediaItem = null; |
| mDropIndex = -1; |
| return endMs; |
| } |
| } else { |
| mDropAfterMediaItem = mediaItem; |
| mDropIndex = i; |
| return endMs; |
| } |
| } else { |
| mDropAfterMediaItem = null; |
| mDropIndex = -1; |
| return endMs; |
| } |
| } |
| |
| beginMs = endMs; |
| } |
| |
| return timeMs; |
| } |
| |
| |
| /** |
| * Adds/edits title overlay of the specified media item. |
| */ |
| private void editOverlay(MovieMediaItem mediaItem) { |
| final Intent intent = new Intent(getContext(), OverlayTitleEditor.class); |
| intent.putExtra(OverlayTitleEditor.PARAM_MEDIA_ITEM_ID, mediaItem.getId()); |
| |
| // Determine if user wants to edit an existing title overlay or add a new one. |
| // Add overlay id and attributes bundle to the extra if the overlay already exists. |
| final MovieOverlay overlay = mediaItem.getOverlay(); |
| if (overlay != null) { |
| final String overlayId = mediaItem.getOverlay().getId(); |
| intent.putExtra(OverlayTitleEditor.PARAM_OVERLAY_ID, overlayId); |
| final Bundle attributes = MovieOverlay.buildUserAttributes( |
| overlay.getType(), overlay.getTitle(), overlay.getSubtitle()); |
| intent.putExtra(OverlayTitleEditor.PARAM_OVERLAY_ATTRIBUTES, |
| attributes); |
| } |
| ((Activity) getContext()).startActivityForResult(intent, |
| VideoEditorActivity.REQUEST_CODE_PICK_OVERLAY); |
| } |
| |
| /** |
| * Removes the overlay of the specified media item. |
| */ |
| private void removeOverlay(MovieMediaItem mediaItem) { |
| final Bundle bundle = new Bundle(); |
| bundle.putString(PARAM_DIALOG_MEDIA_ITEM_ID, mediaItem.getId()); |
| ((Activity) getContext()).showDialog( |
| VideoEditorActivity.DIALOG_REMOVE_OVERLAY_ID, bundle); |
| } |
| |
| /** |
| * Picks a transition. |
| * |
| * @param afterMediaItem After the media item |
| * |
| * @return true if the transition can be inserted |
| */ |
| private boolean pickTransition(MovieMediaItem afterMediaItem) { |
| // Check if the transition would be too short |
| final long transitionDurationMs = getTransitionDuration(afterMediaItem); |
| if (transitionDurationMs < MINIMUM_TRANSITION_DURATION) { |
| Toast.makeText(getContext(), |
| getContext().getString(R.string.editor_transition_too_short), |
| Toast.LENGTH_SHORT).show(); |
| return false; |
| } |
| |
| final String afterMediaId = afterMediaItem != null ? afterMediaItem.getId() : null; |
| final Intent intent = new Intent(getContext(), TransitionsActivity.class); |
| intent.putExtra(TransitionsActivity.PARAM_AFTER_MEDIA_ITEM_ID, afterMediaId); |
| intent.putExtra(TransitionsActivity.PARAM_MINIMUM_DURATION, MINIMUM_TRANSITION_DURATION); |
| intent.putExtra(TransitionsActivity.PARAM_DEFAULT_DURATION, transitionDurationMs); |
| intent.putExtra(TransitionsActivity.PARAM_MAXIMUM_DURATION, |
| getMaxTransitionDuration(afterMediaItem)); |
| ((Activity) getContext()).startActivityForResult(intent, |
| VideoEditorActivity.REQUEST_CODE_PICK_TRANSITION); |
| return true; |
| } |
| |
| /** |
| * Edits a transition. |
| * |
| * @param transition The transition |
| */ |
| private void editTransition(MovieTransition transition) { |
| final MovieMediaItem afterMediaItem = mProject.getPreviousMediaItem(transition); |
| final String afterMediaItemId = afterMediaItem != null ? afterMediaItem.getId() : null; |
| |
| final Intent intent = new Intent(getContext(), TransitionsActivity.class); |
| intent.putExtra(TransitionsActivity.PARAM_AFTER_MEDIA_ITEM_ID, afterMediaItemId); |
| intent.putExtra(TransitionsActivity.PARAM_TRANSITION_ID, transition.getId()); |
| intent.putExtra(TransitionsActivity.PARAM_TRANSITION_TYPE, transition.getType()); |
| intent.putExtra(TransitionsActivity.PARAM_MINIMUM_DURATION, MINIMUM_TRANSITION_DURATION); |
| intent.putExtra(TransitionsActivity.PARAM_DEFAULT_DURATION, transition.getAppDuration()); |
| intent.putExtra(TransitionsActivity.PARAM_MAXIMUM_DURATION, |
| getMaxTransitionDuration(afterMediaItem)); |
| ((Activity)getContext()).startActivityForResult(intent, |
| VideoEditorActivity.REQUEST_CODE_EDIT_TRANSITION); |
| } |
| |
| /** |
| * Finds the media item view with the specified id. |
| * |
| * @param mediaItemId The media item id |
| * @return The found media item view; null if not found |
| */ |
| private View getMediaItemView(String mediaItemId) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object tag = childView.getTag(); |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem mediaItem = (MovieMediaItem)tag; |
| if (mediaItemId.equals(mediaItem.getId())) { |
| return childView; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Finds the media item view index with the specified id. |
| * |
| * @param mediaItemId The media item id |
| * @return The media item view index; -1 if not found |
| */ |
| private int getMediaItemViewIndex(String mediaItemId) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object tag = childView.getTag(); |
| if (tag != null && tag instanceof MovieMediaItem) { |
| final MovieMediaItem mediaItem = (MovieMediaItem)tag; |
| if (mediaItemId.equals(mediaItem.getId())) { |
| return i; |
| } |
| } |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * Finds the transition view with the specified id. |
| * |
| * @param transitionId The transition id |
| * |
| * @return The found transition view; null if not found |
| */ |
| private View getTransitionView(String transitionId) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object tag = childView.getTag(); |
| if (tag != null && tag instanceof MovieTransition) { |
| final MovieTransition transition = (MovieTransition)tag; |
| if (transitionId.equals(transition.getId())) { |
| return childView; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Removes a transition. |
| * |
| * @param transitionId The id of the transition to be removed |
| */ |
| public void removeTransitionView(String transitionId) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final Object tag = getChildAt(i).getTag(); |
| if (tag != null && tag instanceof MovieTransition) { |
| final MovieTransition transition = (MovieTransition)tag; |
| if (transitionId.equals(transition.getId())) { |
| // Remove the view |
| removeViewAt(i); |
| |
| // Adjust the size of all the views |
| requestLayout(); |
| |
| // If this transition was removed by the user invalidate the menu item |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.invalidate(); |
| } |
| return; |
| } |
| } |
| } |
| } |
| |
| /** |
| * Removes all media item and transition views but leave the beginning, end views, and handles. |
| */ |
| private void removeAllMediaItemAndTransitionViews() { |
| int index = 0; |
| while (index < getChildCount()) { |
| final Object tag = getChildAt(index).getTag(); |
| // Media item view or transition view is associated with a media item or transition |
| // attached as a tag. We can thus check the nullity of the tag to determine if it is |
| // media item view or transition view. |
| if (tag != null) { |
| removeViewAt(index); |
| } else { |
| index++; |
| } |
| } |
| requestLayout(); |
| |
| // We cannot add clips by tapping the beginning view. |
| mLeftAddClipButton.setVisibility(View.GONE); |
| } |
| |
| /** |
| * Computes the transition duration. |
| * |
| * @param afterMediaItem The position of the transition |
| * |
| * @return The transition duration |
| */ |
| private long getTransitionDuration(MovieMediaItem afterMediaItem) { |
| if (afterMediaItem == null) { |
| final MovieMediaItem firstMediaItem = mProject.getFirstMediaItem(); |
| return Math.min(MAXIMUM_TRANSITION_DURATION / 2, |
| firstMediaItem.getAppTimelineDuration() / 4); |
| } else if (mProject.isLastMediaItem(afterMediaItem.getId())) { |
| return Math.min(MAXIMUM_TRANSITION_DURATION / 2, |
| afterMediaItem.getAppTimelineDuration() / 4); |
| } else { |
| final MovieMediaItem beforeMediaItem = |
| mProject.getNextMediaItem(afterMediaItem.getId()); |
| final long minDurationMs = Math.min(afterMediaItem.getAppTimelineDuration(), |
| beforeMediaItem.getAppTimelineDuration()); |
| return Math.min(MAXIMUM_TRANSITION_DURATION / 2, minDurationMs / 4); |
| } |
| } |
| |
| /** |
| * Computes the maximum transition duration. |
| * |
| * @param afterMediaItem The position of the transition |
| * |
| * @return The transition duration |
| */ |
| private long getMaxTransitionDuration(MovieMediaItem afterMediaItem) { |
| if (afterMediaItem == null) { |
| final MovieMediaItem firstMediaItem = mProject.getFirstMediaItem(); |
| return Math.min(MAXIMUM_TRANSITION_DURATION, |
| firstMediaItem.getAppTimelineDuration() / 4); |
| } else if (mProject.isLastMediaItem(afterMediaItem.getId())) { |
| return Math.min(MAXIMUM_TRANSITION_DURATION, |
| afterMediaItem.getAppTimelineDuration() / 4); |
| } else { |
| final MovieMediaItem beforeMediaItem = |
| mProject.getNextMediaItem(afterMediaItem.getId()); |
| final long minDurationMs = Math.min(afterMediaItem.getAppTimelineDuration(), |
| beforeMediaItem.getAppTimelineDuration()); |
| return Math.min(MAXIMUM_TRANSITION_DURATION, minDurationMs / 4); |
| } |
| } |
| |
| @Override |
| public void setSelected(boolean selected) { |
| // We only care about when this layout is unselected, which means all children are |
| // unselected. Clients should never call setSelected(true) since it is no-op here. |
| if (selected == false) { |
| closeActionBars(); |
| clearAndHideTrimHandles(); |
| mSelectedView = null; |
| showAddMediaItemButtons(true); |
| } |
| dispatchSetSelected(false); |
| } |
| |
| /** |
| * Returns true if some view item on the timeline is selected. |
| */ |
| public boolean hasItemSelected() { |
| return (mSelectedView != null); |
| } |
| |
| /** |
| * Returns true if some media item is being trimmed by user. |
| */ |
| public boolean isTrimming() { |
| return mIsTrimming; |
| } |
| |
| /** |
| * Closes all contextual action bars. |
| */ |
| private void closeActionBars() { |
| if (mMediaItemActionMode != null) { |
| mMediaItemActionMode.finish(); |
| mMediaItemActionMode = null; |
| } |
| |
| if (mTransitionActionMode != null) { |
| mTransitionActionMode.finish(); |
| mTransitionActionMode = null; |
| } |
| } |
| |
| /** |
| * Hides left and right trim handles and unregisters their listeners. |
| */ |
| private void clearAndHideTrimHandles() { |
| mLeftHandle.setVisibility(View.GONE); |
| mLeftHandle.setListener(null); |
| mRightHandle.setVisibility(View.GONE); |
| mRightHandle.setListener(null); |
| } |
| |
| /** |
| * Unselects the specified view. No-op if the specified view is already unselected. |
| */ |
| private void unSelect(View view) { |
| // Return early if the specified view is already unselected or null. |
| if (view == null || !view.isSelected()) { |
| return; |
| } |
| |
| mSelectedView = null; |
| view.setSelected(false); |
| // Need to redraw other children as well because they had dimmed themselves. |
| invalidateAllChildren(); |
| clearAndHideTrimHandles(); |
| } |
| |
| /** |
| * Selects the specified view and un-selects all others. |
| * No-op if the specified view is already selected. |
| * The selected view will stand out and all other views on the |
| * timeline are dimmed. |
| */ |
| private void select(View selectedView) { |
| // Return early if the view is already selected. |
| if (selectedView.isSelected()) { |
| return; |
| } |
| |
| unselectAllTimelineViews(); |
| mSelectedView = selectedView; |
| mSelectedView.setSelected(true); |
| showAddMediaItemButtons(false); |
| |
| final Object tag = mSelectedView.getTag(); |
| if (tag instanceof MovieMediaItem) { |
| final MediaItemView mediaItemView = (MediaItemView) mSelectedView; |
| if (mediaItemView.isGeneratingEffect()) { |
| mLeftHandle.setEnabled(false); |
| mRightHandle.setEnabled(false); |
| } else { |
| mLeftHandle.setEnabled(true); |
| mRightHandle.setEnabled(true); |
| } |
| |
| final MovieMediaItem mi = (MovieMediaItem) tag; |
| if (mMediaItemActionMode == null) { |
| startActionMode(new MediaItemActionModeCallback(mi)); |
| } |
| |
| final boolean videoClip = mi.isVideoClip(); |
| if (videoClip) { |
| mLeftHandle.setVisibility(View.VISIBLE); |
| mLeftHandle.bringToFront(); |
| mLeftHandle.setLimitReached(mi.getAppBoundaryBeginTime() <= 0, |
| mi.getAppTimelineDuration() <= |
| MediaItemUtils.getMinimumVideoItemDuration()); |
| mLeftHandle.setListener(new HandleView.MoveListener() { |
| private View mTrimmedView; |
| private MovieMediaItem mMediaItem; |
| private long mTransitionsDurationMs; |
| private long mOriginalBeginMs, mOriginalEndMs; |
| private long mMinimumDurationMs; |
| private int mOriginalWidth; |
| private int mMovePosition; |
| |
| @Override |
| public void onMoveBegin(HandleView view) { |
| mMediaItem = (MovieMediaItem)mediaItemView.getTag(); |
| mTransitionsDurationMs = (mMediaItem.getBeginTransition() != null ? |
| mMediaItem.getBeginTransition().getAppDuration() : 0) |
| + (mMediaItem.getEndTransition() != null ? |
| mMediaItem.getEndTransition().getAppDuration() : 0); |
| mOriginalBeginMs = mMediaItem.getAppBoundaryBeginTime(); |
| mOriginalEndMs = mMediaItem.getAppBoundaryEndTime(); |
| mOriginalWidth = mediaItemView.getWidth(); |
| mMinimumDurationMs = MediaItemUtils.getMinimumVideoItemDuration(); |
| setIsTrimming(true); |
| invalidateAllChildren(); |
| mTrimmedView = mediaItemView; |
| |
| mListener.onTrimMediaItemBegin(mMediaItem); |
| if (videoClip) { // Video clip |
| mListener.onTrimMediaItem(mMediaItem, |
| mMediaItem.getAppBoundaryBeginTime()); |
| } else { |
| mListener.onTrimMediaItem(mMediaItem, 0); |
| } |
| // Move the playhead |
| mScrollView.setTag(R.id.playhead_offset, view.getRight()); |
| mScrollView.invalidate(); |
| } |
| |
| @Override |
| public boolean onMove(HandleView view, int left, int delta) { |
| if (mMoveLayoutPending) { |
| return false; |
| } |
| |
| int position = left + delta; |
| mMovePosition = position; |
| // Compute what will become the width of the view |
| int newWidth = mTrimmedView.getRight() - position; |
| if (newWidth == mTrimmedView.getWidth()) { |
| return false; |
| } |
| |
| // Compute the new duration |
| long newDurationMs = mTransitionsDurationMs + |
| (newWidth * mProject.computeDuration()) / |
| (getWidth() - (2 * mHalfParentWidth)); |
| if (Math.abs(mMediaItem.getAppTimelineDuration() - newDurationMs) < |
| TIME_TOLERANCE) { |
| return false; |
| } else if (newDurationMs < Math.max(2 * mTransitionsDurationMs, |
| mMinimumDurationMs)) { |
| newDurationMs = Math.max(2 * mTransitionsDurationMs, |
| mMinimumDurationMs); |
| newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * |
| (getWidth() - (2 * mHalfParentWidth)) / |
| mProject.computeDuration())); |
| position = mTrimmedView.getRight() - newWidth; |
| } else if (mMediaItem.getAppBoundaryEndTime() - newDurationMs < 0) { |
| newDurationMs = mMediaItem.getAppBoundaryEndTime(); |
| newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * |
| (getWidth() - (2 * mHalfParentWidth)) / |
| mProject.computeDuration())); |
| position = mTrimmedView.getRight() - newWidth; |
| } |
| |
| // Return early if the new duration has not changed. We don't have to |
| // adjust the layout. |
| if (newDurationMs == mMediaItem.getAppTimelineDuration()) { |
| return false; |
| } |
| |
| mMediaItem.setAppExtractBoundaries( |
| mMediaItem.getAppBoundaryEndTime() - newDurationMs, |
| mMediaItem.getAppBoundaryEndTime()); |
| |
| mLeftHandle.setLimitReached(mMediaItem.getAppBoundaryBeginTime() <= 0, |
| mMediaItem.getAppTimelineDuration() <= mMinimumDurationMs); |
| mMoveLayoutPending = true; |
| mScrollView.setTag(R.id.left_view_width, |
| mHalfParentWidth - (newWidth - mOriginalWidth)); |
| mScrollView.setTag(R.id.playhead_offset, position); |
| requestLayout(); |
| |
| mListener.onTrimMediaItem(mMediaItem, |
| mMediaItem.getAppBoundaryBeginTime()); |
| return true; |
| } |
| |
| @Override |
| public void onMoveEnd(final HandleView view, final int left, final int delta) { |
| final int position = left + delta; |
| if (mMoveLayoutPending || (position != mMovePosition)) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| if (mMoveLayoutPending) { |
| mHandler.post(this); |
| } else if (position != mMovePosition) { |
| if (onMove(view, left, delta)) { |
| mHandler.post(this); |
| } else { |
| moveDone(); |
| } |
| } else { |
| moveDone(); |
| } |
| } |
| }); |
| } else { |
| moveDone(); |
| } |
| } |
| |
| /** |
| * The move is complete |
| */ |
| private void moveDone() { |
| mScrollView.setTag(R.id.left_view_width, mHalfParentWidth); |
| mScrollView.setTag(R.id.playhead_offset, -1); |
| |
| mListener.onTrimMediaItemEnd(mMediaItem, |
| mMediaItem.getAppBoundaryBeginTime()); |
| mListener.onRequestMovePlayhead(getBeginTime(mMediaItem), false); |
| |
| if (Math.abs(mOriginalBeginMs - mMediaItem.getAppBoundaryBeginTime()) > |
| TIME_TOLERANCE |
| || Math.abs(mOriginalEndMs - mMediaItem.getAppBoundaryEndTime()) > |
| TIME_TOLERANCE) { |
| |
| if (videoClip) { // Video clip |
| ApiService.setMediaItemBoundaries(getContext(), mProject.getPath(), |
| mMediaItem.getId(), mMediaItem.getAppBoundaryBeginTime(), |
| mMediaItem.getAppBoundaryEndTime()); |
| } else { // Image |
| ApiService.setMediaItemDuration(getContext(), mProject.getPath(), |
| mMediaItem.getId(), mMediaItem.getAppTimelineDuration()); |
| } |
| |
| final long durationMs = mMediaItem.getAppTimelineDuration(); |
| mRightHandle.setLimitReached(durationMs <= |
| MediaItemUtils.getMinimumMediaItemDuration(mMediaItem), |
| videoClip ? (mMediaItem.getAppBoundaryEndTime() >= |
| mMediaItem.getDuration()) : durationMs >= |
| MAXIMUM_IMAGE_DURATION); |
| |
| mLeftHandle.setEnabled(false); |
| mRightHandle.setEnabled(false); |
| } |
| setIsTrimming(false); |
| mScrollView.invalidate(); |
| invalidateAllChildren(); |
| } |
| }); |
| } |
| |
| mRightHandle.setVisibility(View.VISIBLE); |
| mRightHandle.bringToFront(); |
| final long durationMs = mi.getAppTimelineDuration(); |
| mRightHandle.setLimitReached( |
| durationMs <= MediaItemUtils.getMinimumMediaItemDuration(mi), |
| videoClip ? (mi.getAppBoundaryEndTime() >= mi.getDuration()) : |
| durationMs >= MAXIMUM_IMAGE_DURATION); |
| mRightHandle.setListener(new HandleView.MoveListener() { |
| private View mTrimmedView; |
| private MovieMediaItem mMediaItem; |
| private long mTransitionsDurationMs; |
| private long mOriginalBeginMs, mOriginalEndMs; |
| private long mMinimumItemDurationMs; |
| private int mMovePosition; |
| |
| @Override |
| public void onMoveBegin(HandleView view) { |
| mMediaItem = (MovieMediaItem)mediaItemView.getTag(); |
| mTransitionsDurationMs = (mMediaItem.getBeginTransition() != null ? |
| mMediaItem.getBeginTransition().getAppDuration() : 0) |
| + (mMediaItem.getEndTransition() != null ? |
| mMediaItem.getEndTransition().getAppDuration() : 0); |
| mOriginalBeginMs = mMediaItem.getAppBoundaryBeginTime(); |
| mOriginalEndMs = mMediaItem.getAppBoundaryEndTime(); |
| mMinimumItemDurationMs = MediaItemUtils.getMinimumMediaItemDuration(mMediaItem); |
| setIsTrimming(true); |
| invalidateAllChildren(); |
| mTrimmedView = mediaItemView; |
| |
| mListener.onTrimMediaItemBegin(mMediaItem); |
| if (videoClip) { // Video clip |
| mListener.onTrimMediaItem(mMediaItem, mMediaItem.getAppBoundaryEndTime()); |
| } else { |
| mListener.onTrimMediaItem(mMediaItem, 0); |
| } |
| |
| // Move the playhead |
| mScrollView.setTag(R.id.playhead_offset, view.getLeft()); |
| mScrollView.invalidate(); |
| } |
| |
| @Override |
| public boolean onMove(HandleView view, int left, int delta) { |
| if (mMoveLayoutPending) { |
| return false; |
| } |
| |
| int position = left + delta; |
| mMovePosition = position; |
| |
| long newDurationMs; |
| // Compute what will become the width of the view |
| int newWidth = position - mTrimmedView.getLeft(); |
| if (newWidth == mTrimmedView.getWidth()) { |
| return false; |
| } |
| |
| // Compute the new duration |
| newDurationMs = mTransitionsDurationMs + |
| (newWidth * mProject.computeDuration()) / |
| (getWidth() - (2 * mHalfParentWidth)); |
| if (Math.abs(mMediaItem.getAppTimelineDuration() - newDurationMs) < |
| TIME_TOLERANCE) { |
| return false; |
| } |
| |
| if (videoClip) { // Video clip |
| if (newDurationMs < Math.max(2 * mTransitionsDurationMs, |
| mMinimumItemDurationMs)) { |
| newDurationMs = Math.max(2 * mTransitionsDurationMs, |
| mMinimumItemDurationMs); |
| newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * |
| (getWidth() - (2 * mHalfParentWidth)) / |
| mProject.computeDuration())); |
| position = newWidth + mTrimmedView.getLeft(); |
| } else if (mMediaItem.getAppBoundaryBeginTime() + newDurationMs > |
| mMediaItem.getDuration()) { |
| newDurationMs = mMediaItem.getDuration() - |
| mMediaItem.getAppBoundaryBeginTime(); |
| newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * |
| (getWidth() - (2 * mHalfParentWidth)) / |
| mProject.computeDuration())); |
| position = newWidth + mTrimmedView.getLeft(); |
| } |
| |
| if (newDurationMs == mMediaItem.getAppTimelineDuration()) { |
| return false; |
| } |
| |
| mMediaItem.setAppExtractBoundaries(mMediaItem.getAppBoundaryBeginTime(), |
| mMediaItem.getAppBoundaryBeginTime() + newDurationMs); |
| mListener.onTrimMediaItem(mMediaItem, mMediaItem.getAppBoundaryEndTime()); |
| } else { // Image |
| if (newDurationMs < Math.max(mMinimumItemDurationMs, |
| 2 * mTransitionsDurationMs)) { |
| newDurationMs = Math.max(mMinimumItemDurationMs, |
| 2 * mTransitionsDurationMs); |
| newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * |
| (getWidth() - (2 * mHalfParentWidth)) / |
| mProject.computeDuration())); |
| position = newWidth + mTrimmedView.getLeft(); |
| } else if (newDurationMs > MAXIMUM_IMAGE_DURATION) { |
| newDurationMs = MAXIMUM_IMAGE_DURATION; |
| newWidth = (int)(((newDurationMs - mTransitionsDurationMs) * |
| (getWidth() - (2 * mHalfParentWidth)) / |
| mProject.computeDuration())); |
| position = newWidth + mTrimmedView.getLeft(); |
| } |
| |
| // Check if the duration would change |
| if (newDurationMs == mMediaItem.getAppTimelineDuration()) { |
| return false; |
| } |
| |
| mMediaItem.setAppExtractBoundaries(0, newDurationMs); |
| mListener.onTrimMediaItem(mMediaItem, 0); |
| } |
| |
| mScrollView.setTag(R.id.playhead_offset, position); |
| mRightHandle.setLimitReached( |
| newDurationMs <= mMinimumItemDurationMs, |
| videoClip ? (mMediaItem.getAppBoundaryEndTime() >= |
| mMediaItem.getDuration()) : newDurationMs >= |
| MAXIMUM_IMAGE_DURATION); |
| |
| mMoveLayoutPending = true; |
| requestLayout(); |
| |
| return true; |
| } |
| |
| @Override |
| public void onMoveEnd(final HandleView view, final int left, final int delta) { |
| final int position = left + delta; |
| if (mMoveLayoutPending || (position != mMovePosition)) { |
| mHandler.post(new Runnable() { |
| @Override |
| public void run() { |
| if (mMoveLayoutPending) { |
| mHandler.post(this); |
| } else if (position != mMovePosition) { |
| if (onMove(view, left, delta)) { |
| mHandler.post(this); |
| } else { |
| moveDone(); |
| } |
| } else { |
| moveDone(); |
| } |
| } |
| }); |
| } else { |
| moveDone(); |
| } |
| } |
| |
| /** |
| * The move is complete |
| */ |
| private void moveDone() { |
| mScrollView.setTag(R.id.playhead_offset, -1); |
| |
| mListener.onTrimMediaItemEnd(mMediaItem, |
| mMediaItem.getAppBoundaryEndTime()); |
| mListener.onRequestMovePlayhead(getEndTime(mMediaItem), false); |
| |
| if (Math.abs(mOriginalBeginMs - mMediaItem.getAppBoundaryBeginTime()) > |
| TIME_TOLERANCE || |
| Math.abs(mOriginalEndMs - mMediaItem.getAppBoundaryEndTime()) > |
| TIME_TOLERANCE) { |
| if (videoClip) { // Video clip |
| ApiService.setMediaItemBoundaries(getContext(), mProject.getPath(), |
| mMediaItem.getId(), mMediaItem.getAppBoundaryBeginTime(), |
| mMediaItem.getAppBoundaryEndTime()); |
| } else { // Image |
| ApiService.setMediaItemDuration(getContext(), mProject.getPath(), |
| mMediaItem.getId(), mMediaItem.getAppTimelineDuration()); |
| } |
| |
| if (videoClip) { |
| mLeftHandle.setLimitReached(mMediaItem.getAppBoundaryBeginTime() <= 0, |
| mMediaItem.getAppTimelineDuration() <= mMinimumItemDurationMs); |
| } |
| |
| mLeftHandle.setEnabled(false); |
| mRightHandle.setEnabled(false); |
| } |
| setIsTrimming(false); |
| mScrollView.invalidate(); |
| invalidateAllChildren(); |
| } |
| }); |
| } else if (tag instanceof MovieTransition) { |
| if (mTransitionActionMode == null) { |
| startActionMode(new TransitionActionModeCallback((MovieTransition) tag)); |
| } |
| } |
| } |
| |
| /** |
| * Indicates if any media item is being trimmed or no. |
| */ |
| private void setIsTrimming(boolean isTrimming) { |
| mIsTrimming = isTrimming; |
| } |
| |
| /** |
| * Sets the playback state for all media item views. |
| * |
| * @param playback indicates if the playback is ongoing |
| */ |
| private void setPlaybackState(boolean playback) { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| final Object tag = childView.getTag(); |
| if (tag != null) { |
| if (tag instanceof MovieMediaItem) { |
| ((MediaItemView) childView).setPlaybackMode(playback); |
| } else if (tag instanceof MovieTransition) { |
| ((TransitionView) childView).setPlaybackMode(playback); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Un-selects all views in the timeline relative layout, including playhead view and |
| * the ones in audio track layout and transition layout. |
| */ |
| private void unselectAllTimelineViews() { |
| ((RelativeLayout) getParent()).setSelected(false); |
| invalidateAllChildren(); |
| } |
| |
| /** |
| * Invalidates all children. Note that invalidating the parent does not invalidate its children. |
| */ |
| private void invalidateAllChildren() { |
| final int childrenCount = getChildCount(); |
| for (int i = 0; i < childrenCount; i++) { |
| final View childView = getChildAt(i); |
| childView.invalidate(); |
| } |
| } |
| |
| /** |
| * Shows or hides "add media buttons" on both sides of the timeline. |
| * |
| * @param show {@code true} to show the "Add media item" buttons, {@code false} to hide them |
| */ |
| private void showAddMediaItemButtons(boolean show) { |
| if (show) { |
| // Shows left add button iff there is at least one media item on the timeline. |
| if (mProject.getMediaItemCount() > 0) { |
| mLeftAddClipButton.setVisibility(View.VISIBLE); |
| } |
| mRightAddClipButton.setVisibility(View.VISIBLE); |
| } else { |
| mLeftAddClipButton.setVisibility(View.GONE); |
| mRightAddClipButton.setVisibility(View.GONE); |
| } |
| } |
| } |