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