MovieControllerOverlay.java revision 7817979db0c52ffeacb951625b1e821eba303285
1/*
2 * Copyright (C) 2011 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.gallery3d.app;
18
19import android.content.Context;
20import android.graphics.Rect;
21import android.os.Handler;
22import android.view.Gravity;
23import android.view.KeyEvent;
24import android.view.MotionEvent;
25import android.view.View;
26import android.view.View.OnClickListener;
27import android.view.animation.Animation;
28import android.view.animation.Animation.AnimationListener;
29import android.view.animation.AnimationUtils;
30import android.widget.FrameLayout;
31import android.widget.ImageView;
32import android.widget.ImageView.ScaleType;
33import android.widget.LinearLayout;
34import android.widget.ProgressBar;
35import android.widget.RelativeLayout;
36import android.widget.TextView;
37
38import com.android.gallery3d.R;
39
40/**
41 * The playback controller for the Movie Player.
42 */
43public class MovieControllerOverlay extends FrameLayout implements
44        ControllerOverlay,
45        OnClickListener,
46        AnimationListener,
47        TimeBar.Listener {
48
49    private enum State {
50        PLAYING,
51        PAUSED,
52        ENDED,
53        ERROR,
54        LOADING
55    }
56
57    private static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6;
58
59    private Listener listener;
60
61    private final View background;
62    private final TimeBar timeBar;
63
64    private View mainView;
65    private final LinearLayout loadingView;
66    private final TextView errorView;
67    private final ImageView playPauseReplayView;
68
69    private final Handler handler;
70    private final Runnable startHidingRunnable;
71    private final Animation hideAnimation;
72
73    private State state;
74
75    private boolean hidden;
76
77    private boolean canReplay = true;
78
79    public MovieControllerOverlay(Context context) {
80        super(context);
81
82        state = State.LOADING;
83
84        LayoutParams wrapContent =
85                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
86        LayoutParams matchParent =
87                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
88
89        background = new View(context);
90        background.setBackgroundColor(context.getResources().getColor(R.color.darker_transparent));
91        addView(background, matchParent);
92
93        timeBar = new TimeBar(context, this);
94        addView(timeBar, wrapContent);
95
96        loadingView = new LinearLayout(context);
97        loadingView.setOrientation(LinearLayout.VERTICAL);
98        loadingView.setGravity(Gravity.CENTER_HORIZONTAL);
99        ProgressBar spinner = new ProgressBar(context);
100        spinner.setIndeterminate(true);
101        loadingView.addView(spinner, wrapContent);
102        TextView loadingText = createOverlayTextView(context);
103        loadingText.setText(R.string.loading_video);
104        loadingView.addView(loadingText, wrapContent);
105        addView(loadingView, wrapContent);
106
107        playPauseReplayView = new ImageView(context);
108        playPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play);
109        playPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol);
110        playPauseReplayView.setScaleType(ScaleType.CENTER);
111        playPauseReplayView.setFocusable(true);
112        playPauseReplayView.setClickable(true);
113        playPauseReplayView.setOnClickListener(this);
114        addView(playPauseReplayView, wrapContent);
115
116        errorView = createOverlayTextView(context);
117        addView(errorView, matchParent);
118
119        handler = new Handler();
120        startHidingRunnable = new Runnable() {
121                @Override
122            public void run() {
123                startHiding();
124            }
125        };
126
127        hideAnimation = AnimationUtils.loadAnimation(context, R.anim.player_out);
128        hideAnimation.setAnimationListener(this);
129
130        RelativeLayout.LayoutParams params =
131                new RelativeLayout.LayoutParams(
132                        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
133        setLayoutParams(params);
134        hide();
135    }
136
137    private TextView createOverlayTextView(Context context) {
138        TextView view = new TextView(context);
139        view.setGravity(Gravity.CENTER);
140        view.setTextColor(0xFFFFFFFF);
141        view.setPadding(0, 15, 0, 15);
142        return view;
143    }
144
145    @Override
146    public void setListener(Listener listener) {
147        this.listener = listener;
148    }
149
150    @Override
151    public void setCanReplay(boolean canReplay) {
152        this.canReplay = canReplay;
153    }
154
155    @Override
156    public View getView() {
157        return this;
158    }
159
160    @Override
161    public void showPlaying() {
162        state = State.PLAYING;
163        showMainView(playPauseReplayView);
164    }
165
166    @Override
167    public void showPaused() {
168        state = State.PAUSED;
169        showMainView(playPauseReplayView);
170    }
171
172    @Override
173    public void showEnded() {
174        state = State.ENDED;
175        showMainView(playPauseReplayView);
176    }
177
178    @Override
179    public void showLoading() {
180        state = State.LOADING;
181        showMainView(loadingView);
182    }
183
184    @Override
185    public void showErrorMessage(String message) {
186        state = State.ERROR;
187        int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING);
188        errorView.setPadding(
189                padding, errorView.getPaddingTop(), padding, errorView.getPaddingBottom());
190        errorView.setText(message);
191        showMainView(errorView);
192    }
193
194    @Override
195    public void setTimes(int currentTime, int totalTime) {
196        timeBar.setTime(currentTime, totalTime);
197    }
198
199    public void hide() {
200        boolean wasHidden = hidden;
201        hidden = true;
202        playPauseReplayView.setVisibility(View.INVISIBLE);
203        loadingView.setVisibility(View.INVISIBLE);
204        background.setVisibility(View.INVISIBLE);
205        timeBar.setVisibility(View.INVISIBLE);
206        setVisibility(View.INVISIBLE);
207        setFocusable(true);
208        requestFocus();
209        if (listener != null && wasHidden != hidden) {
210            listener.onHidden();
211        }
212    }
213
214    private void showMainView(View view) {
215        mainView = view;
216        errorView.setVisibility(mainView == errorView ? View.VISIBLE : View.INVISIBLE);
217        loadingView.setVisibility(mainView == loadingView ? View.VISIBLE : View.INVISIBLE);
218        playPauseReplayView.setVisibility(
219                mainView == playPauseReplayView ? View.VISIBLE : View.INVISIBLE);
220        show();
221    }
222
223    @Override
224    public void show() {
225        boolean wasHidden = hidden;
226        hidden = false;
227        updateViews();
228        setVisibility(View.VISIBLE);
229        setFocusable(false);
230        if (listener != null && wasHidden != hidden) {
231            listener.onShown();
232        }
233        maybeStartHiding();
234    }
235
236    private void maybeStartHiding() {
237        cancelHiding();
238        if (state == State.PLAYING) {
239            handler.postDelayed(startHidingRunnable, 2500);
240        }
241    }
242
243    private void startHiding() {
244        startHideAnimation(background);
245        startHideAnimation(timeBar);
246        startHideAnimation(playPauseReplayView);
247    }
248
249    private void startHideAnimation(View view) {
250        if (view.getVisibility() == View.VISIBLE) {
251            view.startAnimation(hideAnimation);
252        }
253    }
254
255    private void cancelHiding() {
256        handler.removeCallbacks(startHidingRunnable);
257        background.setAnimation(null);
258        timeBar.setAnimation(null);
259        playPauseReplayView.setAnimation(null);
260    }
261
262    @Override
263    public void onAnimationStart(Animation animation) {
264        // Do nothing.
265    }
266
267    @Override
268    public void onAnimationRepeat(Animation animation) {
269        // Do nothing.
270    }
271
272    @Override
273    public void onAnimationEnd(Animation animation) {
274        hide();
275    }
276
277    @Override
278    public void onClick(View view) {
279        if (listener != null) {
280            if (view == playPauseReplayView) {
281                if (state == State.ENDED) {
282                    if (canReplay) {
283                        listener.onReplay();
284                    }
285                } else if (state == State.PAUSED || state == State.PLAYING) {
286                    listener.onPlayPause();
287                }
288            }
289        }
290    }
291
292    @Override
293    public boolean onKeyDown(int keyCode, KeyEvent event) {
294        if (hidden) {
295            show();
296        }
297        return super.onKeyDown(keyCode, event);
298    }
299
300    @Override
301    public boolean onTouchEvent(MotionEvent event) {
302        if (super.onTouchEvent(event)) {
303            return true;
304        }
305
306        if (hidden) {
307            show();
308            return true;
309        }
310        switch (event.getAction()) {
311            case MotionEvent.ACTION_DOWN:
312                cancelHiding();
313                if (state == State.PLAYING || state == State.PAUSED) {
314                    listener.onPlayPause();
315                }
316                break;
317            case MotionEvent.ACTION_UP:
318                maybeStartHiding();
319                break;
320        }
321        return true;
322    }
323
324    // The paddings of 4 sides which covered by system components. E.g.
325    // +-----------------+\
326    // | Action Bar | insets.top
327    // +-----------------+/
328    // | |
329    // | Content Area | insets.right = insets.left = 0
330    // | |
331    // +-----------------+\
332    // | Navigation Bar | insets.bottom
333    // +-----------------+/
334    // Please see View.fitSystemWindows() for more details.
335    private final Rect mWindowInsets = new Rect();
336
337    @Override
338    protected boolean fitSystemWindows(Rect insets) {
339        // We don't set the paddings of this View, otherwise,
340        // the content will get cropped outside window
341        mWindowInsets.set(insets);
342        return true;
343    }
344
345    @Override
346    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
347        Rect insets = mWindowInsets;
348        int pl = insets.left; // the left paddings
349        int pr = insets.right;
350        int pt = insets.top;
351        int pb = insets.bottom;
352
353        int h = bottom - top;
354        int w = right - left;
355        boolean error = errorView.getVisibility() == View.VISIBLE;
356
357        int y = h - pb;
358        // Put both TimeBar and Background just above the bottom system
359        // component.
360        // But extend the background to the width of the screen, since we don't
361        // care if it will be covered by a system component and it looks better.
362        background.layout(0, y - timeBar.getBarHeight(), w, y);
363        timeBar.layout(pl, y - timeBar.getPreferredHeight(), w - pr, y);
364
365        // Needed, otherwise the framework will not re-layout in case only the
366        // padding is changed
367        timeBar.requestLayout();
368
369        // Put the play/pause/next/ previous button in the center of the screen
370        layoutCenteredView(playPauseReplayView, 0, 0, w, h);
371
372        if (mainView != null) {
373            layoutCenteredView(mainView, 0, 0, w, h);
374        }
375    }
376
377    private void layoutCenteredView(View view, int l, int t, int r, int b) {
378        int cw = view.getMeasuredWidth();
379        int ch = view.getMeasuredHeight();
380        int cl = (r - l - cw) / 2;
381        int ct = (b - t - ch) / 2;
382        view.layout(cl, ct, cl + cw, ct + ch);
383    }
384
385    @Override
386    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
387        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
388        measureChildren(widthMeasureSpec, heightMeasureSpec);
389    }
390
391    private void updateViews() {
392        if (hidden) {
393            return;
394        }
395        background.setVisibility(View.VISIBLE);
396        timeBar.setVisibility(View.VISIBLE);
397        playPauseReplayView.setImageResource(
398                state == State.PAUSED ? R.drawable.ic_vidcontrol_play :
399                state == State.PLAYING ? R.drawable.ic_vidcontrol_pause :
400                R.drawable.ic_vidcontrol_reload);
401        playPauseReplayView.setVisibility(
402                (state != State.LOADING && state != State.ERROR &&
403                !(state == State.ENDED && !canReplay))
404                ? View.VISIBLE : View.GONE);
405        requestLayout();
406    }
407
408    // TimeBar listener
409
410    @Override
411    public void onScrubbingStart() {
412        cancelHiding();
413        listener.onSeekStart();
414    }
415
416    @Override
417    public void onScrubbingMove(int time) {
418        cancelHiding();
419        listener.onSeekMove(time);
420    }
421
422    @Override
423    public void onScrubbingEnd(int time) {
424        maybeStartHiding();
425        listener.onSeekEnd(time);
426    }
427}
428