1/*
2 * Copyright (C) 2012 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.content.res.Resources;
21import android.graphics.Rect;
22import android.view.Gravity;
23import android.view.KeyEvent;
24import android.view.MotionEvent;
25import android.view.View;
26import android.view.View.OnClickListener;
27import android.widget.FrameLayout;
28import android.widget.ImageView;
29import android.widget.ImageView.ScaleType;
30import android.widget.LinearLayout;
31import android.widget.ProgressBar;
32import android.widget.RelativeLayout;
33import android.widget.TextView;
34
35import com.android.gallery3d.R;
36
37/**
38 * The common playback controller for the Movie Player or Video Trimming.
39 */
40public abstract class CommonControllerOverlay extends FrameLayout implements
41        ControllerOverlay,
42        OnClickListener,
43        TimeBar.Listener {
44
45    protected enum State {
46        PLAYING,
47        PAUSED,
48        ENDED,
49        ERROR,
50        LOADING
51    }
52
53    private static final float ERROR_MESSAGE_RELATIVE_PADDING = 1.0f / 6;
54
55    protected Listener mListener;
56
57    protected final View mBackground;
58    protected TimeBar mTimeBar;
59
60    protected View mMainView;
61    protected final LinearLayout mLoadingView;
62    protected final TextView mErrorView;
63    protected final ImageView mPlayPauseReplayView;
64
65    protected State mState;
66
67    protected boolean mCanReplay = true;
68
69    public void setSeekable(boolean canSeek) {
70        mTimeBar.setSeekable(canSeek);
71    }
72
73    public CommonControllerOverlay(Context context) {
74        super(context);
75
76        mState = State.LOADING;
77        // TODO: Move the following layout code into xml file.
78        LayoutParams wrapContent =
79                new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
80        LayoutParams matchParent =
81                new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
82
83        mBackground = new View(context);
84        mBackground.setBackgroundColor(context.getResources().getColor(R.color.darker_transparent));
85        addView(mBackground, matchParent);
86
87        // Depending on the usage, the timeBar can show a single scrubber, or
88        // multiple ones for trimming.
89        createTimeBar(context);
90        addView(mTimeBar, wrapContent);
91        mTimeBar.setContentDescription(
92                context.getResources().getString(R.string.accessibility_time_bar));
93        mLoadingView = new LinearLayout(context);
94        mLoadingView.setOrientation(LinearLayout.VERTICAL);
95        mLoadingView.setGravity(Gravity.CENTER_HORIZONTAL);
96        ProgressBar spinner = new ProgressBar(context);
97        spinner.setIndeterminate(true);
98        mLoadingView.addView(spinner, wrapContent);
99        TextView loadingText = createOverlayTextView(context);
100        loadingText.setText(R.string.loading_video);
101        mLoadingView.addView(loadingText, wrapContent);
102        addView(mLoadingView, wrapContent);
103
104        mPlayPauseReplayView = new ImageView(context);
105        mPlayPauseReplayView.setImageResource(R.drawable.ic_vidcontrol_play);
106        mPlayPauseReplayView.setContentDescription(
107                context.getResources().getString(R.string.accessibility_play_video));
108        mPlayPauseReplayView.setBackgroundResource(R.drawable.bg_vidcontrol);
109        mPlayPauseReplayView.setScaleType(ScaleType.CENTER);
110        mPlayPauseReplayView.setFocusable(true);
111        mPlayPauseReplayView.setClickable(true);
112        mPlayPauseReplayView.setOnClickListener(this);
113        addView(mPlayPauseReplayView, wrapContent);
114
115        mErrorView = createOverlayTextView(context);
116        addView(mErrorView, matchParent);
117
118        RelativeLayout.LayoutParams params =
119                new RelativeLayout.LayoutParams(
120                        LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
121        setLayoutParams(params);
122        hide();
123    }
124
125    abstract protected void createTimeBar(Context context);
126
127    private TextView createOverlayTextView(Context context) {
128        TextView view = new TextView(context);
129        view.setGravity(Gravity.CENTER);
130        view.setTextColor(0xFFFFFFFF);
131        view.setPadding(0, 15, 0, 15);
132        return view;
133    }
134
135    @Override
136    public void setListener(Listener listener) {
137        this.mListener = listener;
138    }
139
140    @Override
141    public void setCanReplay(boolean canReplay) {
142        this.mCanReplay = canReplay;
143    }
144
145    @Override
146    public View getView() {
147        return this;
148    }
149
150    @Override
151    public void showPlaying() {
152        mState = State.PLAYING;
153        showMainView(mPlayPauseReplayView);
154    }
155
156    @Override
157    public void showPaused() {
158        mState = State.PAUSED;
159        showMainView(mPlayPauseReplayView);
160    }
161
162    @Override
163    public void showEnded() {
164        mState = State.ENDED;
165        if (mCanReplay) showMainView(mPlayPauseReplayView);
166    }
167
168    @Override
169    public void showLoading() {
170        mState = State.LOADING;
171        showMainView(mLoadingView);
172    }
173
174    @Override
175    public void showErrorMessage(String message) {
176        mState = State.ERROR;
177        int padding = (int) (getMeasuredWidth() * ERROR_MESSAGE_RELATIVE_PADDING);
178        mErrorView.setPadding(
179                padding, mErrorView.getPaddingTop(), padding, mErrorView.getPaddingBottom());
180        mErrorView.setText(message);
181        showMainView(mErrorView);
182    }
183
184    @Override
185    public void setTimes(int currentTime, int totalTime,
186            int trimStartTime, int trimEndTime) {
187        mTimeBar.setTime(currentTime, totalTime, trimStartTime, trimEndTime);
188    }
189
190    public void hide() {
191        mPlayPauseReplayView.setVisibility(View.INVISIBLE);
192        mLoadingView.setVisibility(View.INVISIBLE);
193        mBackground.setVisibility(View.INVISIBLE);
194        mTimeBar.setVisibility(View.INVISIBLE);
195        setVisibility(View.INVISIBLE);
196        setFocusable(true);
197        requestFocus();
198    }
199
200    private void showMainView(View view) {
201        mMainView = view;
202        mErrorView.setVisibility(mMainView == mErrorView ? View.VISIBLE : View.INVISIBLE);
203        mLoadingView.setVisibility(mMainView == mLoadingView ? View.VISIBLE : View.INVISIBLE);
204        mPlayPauseReplayView.setVisibility(
205                mMainView == mPlayPauseReplayView ? View.VISIBLE : View.INVISIBLE);
206        show();
207    }
208
209    @Override
210    public void show() {
211        updateViews();
212        setVisibility(View.VISIBLE);
213        setFocusable(false);
214    }
215
216    @Override
217    public void onClick(View view) {
218        if (mListener != null) {
219            if (view == mPlayPauseReplayView) {
220                if (mState == State.ENDED) {
221                    if (mCanReplay) {
222                        mListener.onReplay();
223                    }
224                } else if (mState == State.PAUSED || mState == State.PLAYING) {
225                    mListener.onPlayPause();
226                }
227            }
228        }
229    }
230
231    @Override
232    public boolean onKeyDown(int keyCode, KeyEvent event) {
233        return super.onKeyDown(keyCode, event);
234    }
235
236    @Override
237    public boolean onTouchEvent(MotionEvent event) {
238        if (super.onTouchEvent(event)) {
239            return true;
240        }
241        return false;
242    }
243
244    // The paddings of 4 sides which covered by system components. E.g.
245    // +-----------------+\
246    // | Action Bar | insets.top
247    // +-----------------+/
248    // | |
249    // | Content Area | insets.right = insets.left = 0
250    // | |
251    // +-----------------+\
252    // | Navigation Bar | insets.bottom
253    // +-----------------+/
254    // Please see View.fitSystemWindows() for more details.
255    private final Rect mWindowInsets = new Rect();
256
257    @Override
258    protected boolean fitSystemWindows(Rect insets) {
259        // We don't set the paddings of this View, otherwise,
260        // the content will get cropped outside window
261        mWindowInsets.set(insets);
262        return true;
263    }
264
265    @Override
266    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
267        Rect insets = mWindowInsets;
268        int pl = insets.left; // the left paddings
269        int pr = insets.right;
270        int pt = insets.top;
271        int pb = insets.bottom;
272
273        int h = bottom - top;
274        int w = right - left;
275        boolean error = mErrorView.getVisibility() == View.VISIBLE;
276
277        int y = h - pb;
278        // Put both TimeBar and Background just above the bottom system
279        // component.
280        // But extend the background to the width of the screen, since we don't
281        // care if it will be covered by a system component and it looks better.
282        mBackground.layout(0, y - mTimeBar.getBarHeight(), w, y);
283        mTimeBar.layout(pl, y - mTimeBar.getPreferredHeight(), w - pr, y);
284
285        // Put the play/pause/next/ previous button in the center of the screen
286        layoutCenteredView(mPlayPauseReplayView, 0, 0, w, h);
287
288        if (mMainView != null) {
289            layoutCenteredView(mMainView, 0, 0, w, h);
290        }
291    }
292
293    private void layoutCenteredView(View view, int l, int t, int r, int b) {
294        int cw = view.getMeasuredWidth();
295        int ch = view.getMeasuredHeight();
296        int cl = (r - l - cw) / 2;
297        int ct = (b - t - ch) / 2;
298        view.layout(cl, ct, cl + cw, ct + ch);
299    }
300
301    @Override
302    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
303        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
304        measureChildren(widthMeasureSpec, heightMeasureSpec);
305    }
306
307    protected void updateViews() {
308        mBackground.setVisibility(View.VISIBLE);
309        mTimeBar.setVisibility(View.VISIBLE);
310        Resources resources = getContext().getResources();
311        int imageResource = R.drawable.ic_vidcontrol_reload;
312        String contentDescription = resources.getString(R.string.accessibility_reload_video);
313        if (mState == State.PAUSED) {
314            imageResource = R.drawable.ic_vidcontrol_play;
315            contentDescription = resources.getString(R.string.accessibility_play_video);
316        } else if (mState == State.PLAYING) {
317            imageResource = R.drawable.ic_vidcontrol_pause;
318            contentDescription = resources.getString(R.string.accessibility_pause_video);
319        }
320
321        mPlayPauseReplayView.setImageResource(imageResource);
322        mPlayPauseReplayView.setContentDescription(contentDescription);
323        mPlayPauseReplayView.setVisibility(
324                (mState != State.LOADING && mState != State.ERROR &&
325                !(mState == State.ENDED && !mCanReplay))
326                ? View.VISIBLE : View.GONE);
327        requestLayout();
328    }
329
330    // TimeBar listener
331
332    @Override
333    public void onScrubbingStart() {
334        mListener.onSeekStart();
335    }
336
337    @Override
338    public void onScrubbingMove(int time) {
339        mListener.onSeekMove(time);
340    }
341
342    @Override
343    public void onScrubbingEnd(int time, int trimStartTime, int trimEndTime) {
344        mListener.onSeekEnd(time, trimStartTime, trimEndTime);
345    }
346}
347