1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.videoeditor.widgets;
18
19import com.android.videoeditor.service.ApiService;
20import com.android.videoeditor.service.MovieTransition;
21import com.android.videoeditor.R;
22
23import android.content.Context;
24import android.content.res.Resources;
25import android.graphics.Bitmap;
26import android.graphics.Canvas;
27import android.graphics.Color;
28import android.graphics.Paint;
29import android.graphics.Rect;
30import android.util.AttributeSet;
31import android.util.DisplayMetrics;
32import android.util.Log;
33import android.view.Display;
34import android.view.GestureDetector;
35import android.view.MotionEvent;
36import android.view.View;
37import android.view.WindowManager;
38import android.widget.ImageView;
39
40/**
41 * Transition view. This class assumes transition is always put on a MediaLinearLayout and is
42 * wrapped with a timeline scroll view.
43 */
44public class TransitionView extends ImageView {
45    // Logging
46    private static final String TAG = "TransitionView";
47
48    // Instance variables
49    private final GestureDetector mSimpleGestureDetector;
50    private final ScrollViewListener mScrollListener;
51    private final Rect mGeneratingTransitionProgressDestRect;
52    private final Paint mSeparatorPaint;
53    private boolean mIsScrolling;
54    // Convenient handle to the parent timeline scroll view.
55    private TimelineHorizontalScrollView mScrollView;
56    // Convenient handle to the parent timeline linear layout.
57    private MediaLinearLayout mTimeline;
58    private int mScrollX;
59    private int mScreenWidth;
60    private String mProjectPath;
61    private Bitmap[] mBitmaps;
62    private ItemSimpleGestureListener mGestureListener;
63    private int mGeneratingTransitionProgress;
64    private boolean mIsPlaying;
65
66    public TransitionView(Context context, AttributeSet attrs, int defStyle) {
67        super(context, attrs, defStyle);
68
69        // Setup the gesture listener
70        mSimpleGestureDetector = new GestureDetector(context,
71                new GestureDetector.SimpleOnGestureListener() {
72                    @Override
73                    public boolean onSingleTapConfirmed(MotionEvent e) {
74                        if (mGestureListener != null) {
75                            return mGestureListener.onSingleTapConfirmed(TransitionView.this, -1,
76                                    e);
77                        } else {
78                            return false;
79                        }
80                    }
81
82                    @Override
83                    public void onLongPress(MotionEvent e) {
84                        if (mGestureListener != null) {
85                            mGestureListener.onLongPress(TransitionView.this, e);
86                        }
87                    }
88                });
89
90        mScrollListener = new ScrollViewListener() {
91            @Override
92            public void onScrollBegin(View view, int scrollX, int scrollY, boolean appScroll) {
93                mIsScrolling = true;
94            }
95
96            @Override
97            public void onScrollProgress(View view, int scrollX, int scrollY, boolean appScroll) {
98                invalidate();
99            }
100
101            @Override
102            public void onScrollEnd(View view, int scrollX, int scrollY, boolean appScroll) {
103                mIsScrolling = false;
104                mScrollX = scrollX;
105
106                if (requestThumbnails()) {
107                    invalidate();
108                }
109            }
110        };
111
112        final Resources resources = getResources();
113        // Prepare the bitmap rectangles
114        final ProgressBar progressBar = ProgressBar.getProgressBar(context);
115        final int layoutHeight = (int)(resources.getDimension(R.dimen.media_layout_height) -
116                resources.getDimension(R.dimen.media_layout_padding) -
117                (2 * resources.getDimension(R.dimen.timelime_transition_vertical_inset)));
118        mGeneratingTransitionProgressDestRect = new Rect(getPaddingLeft(),
119                layoutHeight - progressBar.getHeight() - getPaddingBottom(), 0,
120                layoutHeight - getPaddingBottom());
121
122        // Initialize the progress value
123        mGeneratingTransitionProgress = -1;
124
125        // Get the screen width
126        final Display display = ((WindowManager) getContext().getSystemService(
127                Context.WINDOW_SERVICE)).getDefaultDisplay();
128        final DisplayMetrics metrics = new DisplayMetrics();
129        display.getMetrics(metrics);
130        mScreenWidth = metrics.widthPixels;
131
132        // Prepare the separator paint
133        mSeparatorPaint = new Paint();
134        mSeparatorPaint.setColor(Color.BLACK);
135        mSeparatorPaint.setStrokeWidth(2);
136    }
137
138    public TransitionView(Context context, AttributeSet attrs) {
139        this(context, attrs, 0);
140    }
141
142    public TransitionView(Context context) {
143        this(context, null, 0);
144    }
145
146    @Override
147    protected void onAttachedToWindow() {
148        // Add the horizontal scroll view listener
149        mScrollView = (TimelineHorizontalScrollView) getRootView().findViewById(
150                R.id.timeline_scroller);
151        mScrollView.addScrollListener(mScrollListener);
152        mScrollX = mScrollView.getScrollX();
153
154        mTimeline = (MediaLinearLayout) getRootView().findViewById(R.id.timeline_media);
155    }
156
157    @Override
158    protected void onDetachedFromWindow() {
159        // Remove the horizontal scroll listener
160        mScrollView.removeScrollListener(mScrollListener);
161
162        // Release the current set of bitmaps
163        if (mBitmaps != null) {
164            for (int i = 0; i < mBitmaps.length; i++) {
165                if (mBitmaps[i] != null) {
166                    mBitmaps[i].recycle();
167                }
168            }
169
170            mBitmaps = null;
171        }
172    }
173
174    /**
175     * @param projectPath The project path
176     */
177    public void setProjectPath(String projectPath) {
178        mProjectPath = projectPath;
179    }
180
181    /**
182     * @param listener The gesture listener
183     */
184    public void setGestureListener(ItemSimpleGestureListener listener) {
185        mGestureListener = listener;
186    }
187
188    /**
189     * Resets the transition generation progress.
190     */
191    public void resetGeneratingTransitionProgress() {
192        setGeneratingTransitionProgress(-1);
193    }
194
195    /**
196     * Sets the transition generation progress.
197     */
198    public void setGeneratingTransitionProgress(int progress) {
199        if (progress == 100) {
200            // Request the preview bitmaps
201            requestThumbnails();
202            mGeneratingTransitionProgress = -1;
203        } else {
204            mGeneratingTransitionProgress = progress;
205        }
206
207        invalidate();
208    }
209
210    /**
211     * @return true if generation is in progress
212     */
213    public boolean isGeneratingTransition() {
214        return (mGeneratingTransitionProgress >= 0);
215    }
216
217    /**
218     * A view enters or exits the playback mode
219     *
220     * @param playback true if playback is in progress
221     */
222    public void setPlaybackMode(boolean playback) {
223        mIsPlaying = playback;
224    }
225
226    /**
227     * @param bitmaps The bitmaps array
228     *
229     * @return true if the bitmaps were used
230     */
231    public boolean setBitmaps(Bitmap[] bitmaps) {
232        if (mGeneratingTransitionProgress >= 0) {
233            return false;
234        }
235
236        // Release the current set of bitmaps
237        if (mBitmaps != null) {
238            for (int i = 0; i < mBitmaps.length; i++) {
239                if (mBitmaps[i] != null) {
240                    mBitmaps[i].recycle();
241                }
242            }
243        }
244
245        mBitmaps = bitmaps;
246        invalidate();
247
248        return true;
249    }
250
251    @Override
252    protected void onDraw(Canvas canvas) {
253        super.onDraw(canvas);
254
255        // If the view is too small, don't draw anything.
256        if (getWidth() <= getPaddingLeft() + getPaddingRight()) {
257            return;
258        }
259
260        if (mGeneratingTransitionProgress >= 0) {
261            ProgressBar.getProgressBar(getContext()).draw(canvas, mGeneratingTransitionProgress,
262                    mGeneratingTransitionProgressDestRect, getPaddingLeft(),
263                    getWidth() - getPaddingRight());
264        } else if (mBitmaps != null) {
265            final int halfWidth = getWidth() / 2;
266            // Draw the bitmaps
267            // Draw the left side of the transition
268            canvas.save();
269            canvas.clipRect(getPaddingLeft(), getPaddingTop(), halfWidth,
270                    getHeight() - getPaddingBottom());
271
272            if (mBitmaps[0] != null) {
273                canvas.drawBitmap(mBitmaps[0], getPaddingLeft(), getPaddingTop(), null);
274            } else {
275                canvas.drawColor(Color.BLACK);
276            }
277            canvas.restore();
278
279            // Draw the right side of the transition
280            canvas.save();
281            canvas.clipRect(halfWidth, getPaddingTop(),
282                    getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
283            if (mBitmaps[1] != null) {
284                canvas.drawBitmap(mBitmaps[1],
285                        getWidth() - getPaddingRight() - mBitmaps[1].getWidth(), getPaddingTop(),
286                        null);
287            } else {
288                canvas.drawColor(Color.BLACK);
289            }
290            canvas.restore();
291
292            canvas.drawLine(halfWidth, getPaddingTop(), halfWidth,
293                    getHeight() - getPaddingBottom(), mSeparatorPaint);
294
295            // Dim myself if some view on the timeline is selected but not me
296            // by drawing a transparent black overlay.
297            if (!isSelected() && mTimeline.hasItemSelected()) {
298                final Paint paint = new Paint();
299                paint.setColor(Color.BLACK);
300                paint.setAlpha(192);
301                canvas.drawPaint(paint);
302            }
303        } else if (mIsPlaying) { // Playing
304        } else if (mIsScrolling) { // Scrolling
305        } else { // Not scrolling and not playing
306            requestThumbnails();
307        }
308    }
309
310    @Override
311    public boolean onTouchEvent(MotionEvent ev) {
312        // Let the gesture detector inspect all events.
313        mSimpleGestureDetector.onTouchEvent(ev);
314        return super.onTouchEvent(ev);
315    }
316
317    /**
318     * Request thumbnails if necessary
319     *
320     * @return true if the bitmaps already exist
321     */
322    private boolean requestThumbnails() {
323        // Check if we already have the bitmaps
324        if (mBitmaps != null) {
325            return true;
326        }
327
328        // Do not request thumbnails during playback
329        if (mIsScrolling) {
330            return false;
331        }
332
333        final MovieTransition transition = (MovieTransition)getTag();
334        // Check if we already requested the thumbnails
335        if (ApiService.isTransitionThumbnailsPending(mProjectPath, transition.getId())) {
336            return false;
337        }
338
339        final int start = getLeft() + getPaddingLeft() - mScrollX;
340        final int end = getRight() - getPaddingRight() - mScrollX;
341
342        if (start >= mScreenWidth || end < 0 || start == end) {
343            if (Log.isLoggable(TAG, Log.VERBOSE)) {
344                Log.v(TAG, "Transition view is off screen: " + transition.getId() +
345                        ", from: " + start + " to " + end);
346            }
347
348            // Release the current set of bitmaps
349            if (mBitmaps != null) {
350                for (int i = 0; i < mBitmaps.length; i++) {
351                    if (mBitmaps[i] != null) {
352                        mBitmaps[i].recycle();
353                    }
354                }
355
356                mBitmaps = null;
357            }
358
359            return false;
360        }
361
362        // Compute the thumbnail width
363        final int thumbnailHeight = getHeight() - getPaddingTop() - getPaddingBottom();
364        // Request the thumbnails
365        ApiService.getTransitionThumbnails(getContext(), mProjectPath, transition.getId(),
366                thumbnailHeight);
367
368        return false;
369    }
370}
371