PlaybackOverlayFragment.java revision 2452cde3b8d7cbe62f6eb2fbcbcf9a02448d6891
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5 * in compliance with the License. You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the License
10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11 * or implied. See the License for the specific language governing permissions and limitations under
12 * the License.
13 */
14package android.support.v17.leanback.app;
15
16import android.animation.Animator;
17import android.animation.AnimatorInflater;
18import android.animation.TimeInterpolator;
19import android.animation.ValueAnimator;
20import android.animation.ValueAnimator.AnimatorUpdateListener;
21import android.content.Context;
22import android.graphics.Color;
23import android.graphics.drawable.ColorDrawable;
24import android.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.support.v17.leanback.R;
28import android.support.v17.leanback.animation.LogAccelerateInterpolator;
29import android.support.v17.leanback.animation.LogDecelerateInterpolator;
30import android.support.v17.leanback.media.PlaybackGlueHost;
31import android.support.v17.leanback.widget.ItemBridgeAdapter;
32import android.support.v17.leanback.widget.ObjectAdapter;
33import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
34import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
35import android.support.v17.leanback.widget.Presenter;
36import android.support.v17.leanback.widget.PresenterSelector;
37import android.support.v17.leanback.widget.RowPresenter;
38import android.support.v17.leanback.widget.VerticalGridView;
39import android.support.v7.widget.RecyclerView;
40import android.util.Log;
41import android.view.InputEvent;
42import android.view.KeyEvent;
43import android.view.LayoutInflater;
44import android.view.MotionEvent;
45import android.view.View;
46import android.view.ViewGroup;
47import android.view.animation.AccelerateInterpolator;
48
49import java.lang.ref.WeakReference;
50import java.util.ArrayList;
51
52/**
53 * A fragment for displaying playback controls and related content.
54 * <p>
55 * A PlaybackOverlayFragment renders the elements of its {@link ObjectAdapter} as a set
56 * of rows in a vertical list.  The Adapter's {@link PresenterSelector} must maintain subclasses
57 * of {@link RowPresenter}.
58 * </p>
59 * <p>
60 * An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
61 * at position 0 in the adapter.
62 * </p>
63 * <p>
64 *  This class is now deprecated, please us
65 * </p>
66 * @deprecated Use {@link PlaybackFragment}.
67 */
68@Deprecated
69public class PlaybackOverlayFragment extends DetailsFragment {
70
71    /**
72     * No background.
73     */
74    public static final int BG_NONE = 0;
75
76    /**
77     * A dark translucent background.
78     */
79    public static final int BG_DARK = 1;
80
81    /**
82     * A light translucent background.
83     */
84    public static final int BG_LIGHT = 2;
85
86    /**
87     * Listener allowing the application to receive notification of fade in and/or fade out
88     * completion events.
89     */
90    public static class OnFadeCompleteListener {
91        public void onFadeInComplete() {
92        }
93        public void onFadeOutComplete() {
94        }
95    }
96
97    static final String TAG = "PlaybackOverlayFragment";
98    static final boolean DEBUG = false;
99    private static final int ANIMATION_MULTIPLIER = 1;
100
101    static int START_FADE_OUT = 1;
102
103    // Fading status
104    static final int IDLE = 0;
105    private static final int IN = 1;
106    static final int OUT = 2;
107
108    private int mPaddingTop;
109    private int mPaddingBottom;
110    private View mRootView;
111    private int mBackgroundType = BG_DARK;
112    private int mBgDarkColor;
113    private int mBgLightColor;
114    private int mShowTimeMs;
115    private int mMajorFadeTranslateY, mMinorFadeTranslateY;
116    int mAnimationTranslateY;
117    OnFadeCompleteListener mFadeCompleteListener;
118    private PlaybackControlGlue.InputEventHandler mInputEventHandler;
119    boolean mFadingEnabled = true;
120    int mFadingStatus = IDLE;
121    int mBgAlpha;
122    private ValueAnimator mBgFadeInAnimator, mBgFadeOutAnimator;
123    private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator;
124    private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator;
125    private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator;
126    boolean mResetControlsToPrimaryActionsPending;
127    PlaybackGlueHost.HostCallback mHostCallback;
128
129    private final Animator.AnimatorListener mFadeListener =
130            new Animator.AnimatorListener() {
131        @Override
132        public void onAnimationStart(Animator animation) {
133            enableVerticalGridAnimations(false);
134        }
135        @Override
136        public void onAnimationRepeat(Animator animation) {
137        }
138        @Override
139        public void onAnimationCancel(Animator animation) {
140        }
141        @Override
142        public void onAnimationEnd(Animator animation) {
143            if (DEBUG) Log.v(TAG, "onAnimationEnd " + mBgAlpha);
144            if (mBgAlpha > 0) {
145                enableVerticalGridAnimations(true);
146                startFadeTimer();
147                if (mFadeCompleteListener != null) {
148                    mFadeCompleteListener.onFadeInComplete();
149                }
150            } else {
151                VerticalGridView verticalView = getVerticalGridView();
152                // reset focus to the primary actions only if the selected row was the controls row
153                if (verticalView != null && verticalView.getSelectedPosition() == 0) {
154                    resetControlsToPrimaryActions(null);
155                }
156                if (mFadeCompleteListener != null) {
157                    mFadeCompleteListener.onFadeOutComplete();
158                }
159            }
160            mFadingStatus = IDLE;
161        }
162    };
163
164    static class FadeHandler extends Handler {
165        @Override
166        public void handleMessage(Message message) {
167            PlaybackOverlayFragment fragment;
168            if (message.what == START_FADE_OUT) {
169                fragment = ((WeakReference<PlaybackOverlayFragment>) message.obj).get();
170                if (fragment != null && fragment.mFadingEnabled) {
171                    fragment.fade(false);
172                }
173            }
174        }
175    }
176
177    static final Handler sHandler = new FadeHandler();
178
179    final WeakReference<PlaybackOverlayFragment> mFragmentReference =  new WeakReference(this);
180
181    private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener =
182            new VerticalGridView.OnTouchInterceptListener() {
183        @Override
184        public boolean onInterceptTouchEvent(MotionEvent event) {
185            return onInterceptInputEvent(event);
186        }
187    };
188
189    private final VerticalGridView.OnKeyInterceptListener mOnKeyInterceptListener =
190            new VerticalGridView.OnKeyInterceptListener() {
191        @Override
192        public boolean onInterceptKeyEvent(KeyEvent event) {
193            return onInterceptInputEvent(event);
194        }
195    };
196
197    void setBgAlpha(int alpha) {
198        mBgAlpha = alpha;
199        if (mRootView != null) {
200            mRootView.getBackground().setAlpha(alpha);
201        }
202    }
203
204    void enableVerticalGridAnimations(boolean enable) {
205        if (getVerticalGridView() != null) {
206            getVerticalGridView().setAnimateChildLayout(enable);
207        }
208    }
209
210    void resetControlsToPrimaryActions(ItemBridgeAdapter.ViewHolder vh) {
211        if (vh == null && getVerticalGridView() != null) {
212            vh = (ItemBridgeAdapter.ViewHolder) getVerticalGridView().findViewHolderForPosition(0);
213        }
214        if (vh == null) {
215            mResetControlsToPrimaryActionsPending = true;
216        } else if (vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
217            mResetControlsToPrimaryActionsPending = false;
218            ((PlaybackControlsRowPresenter) vh.getPresenter()).showPrimaryActions(
219                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder());
220        }
221    }
222
223    /**
224     * Enables or disables view fading.  If enabled,
225     * the view will be faded in when the fragment starts,
226     * and will fade out after a time period.  The timeout
227     * period is reset each time {@link #tickle} is called.
228     *
229     */
230    public void setFadingEnabled(boolean enabled) {
231        if (DEBUG) Log.v(TAG, "setFadingEnabled " + enabled);
232        if (enabled != mFadingEnabled) {
233            mFadingEnabled = enabled;
234            if (mFadingEnabled) {
235                if (isResumed() && mFadingStatus == IDLE
236                        && !sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
237                    startFadeTimer();
238                }
239            } else {
240                // Ensure fully opaque
241                sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
242                fade(true);
243            }
244        }
245    }
246
247    /**
248     * Returns true if view fading is enabled.
249     */
250    public boolean isFadingEnabled() {
251        return mFadingEnabled;
252    }
253
254    /**
255     * Sets the listener to be called when fade in or out has completed.
256     */
257    public void setFadeCompleteListener(OnFadeCompleteListener listener) {
258        mFadeCompleteListener = listener;
259    }
260
261    /**
262     * Returns the listener to be called when fade in or out has completed.
263     */
264    public OnFadeCompleteListener getFadeCompleteListener() {
265        return mFadeCompleteListener;
266    }
267
268    @Deprecated
269    public interface InputEventHandler extends PlaybackControlGlue.InputEventHandler {
270    }
271
272    /**
273     * Sets the input event handler.
274     */
275    @Deprecated
276    public final void setInputEventHandler(InputEventHandler handler) {
277        mInputEventHandler = handler;
278    }
279
280    /**
281     * Returns the input event handler.
282     */
283    @Deprecated
284    public final InputEventHandler getInputEventHandler() {
285        return (InputEventHandler)mInputEventHandler;
286    }
287
288    /**
289     * Sets the input event handler.
290     */
291    public final void setEventHandler(PlaybackControlGlue.InputEventHandler handler) {
292        mInputEventHandler = handler;
293    }
294
295    /**
296     * Returns the input event handler.
297     */
298    public final PlaybackControlGlue.InputEventHandler getEventHandler() {
299        return mInputEventHandler;
300    }
301
302    /**
303     * Tickles the playback controls.  Fades in the view if it was faded out,
304     * otherwise resets the fade out timer.  Tickling on input events is handled
305     * by the fragment.
306     */
307    public void tickle() {
308        if (DEBUG) Log.v(TAG, "tickle enabled " + mFadingEnabled + " isResumed " + isResumed());
309        if (!mFadingEnabled || !isResumed()) {
310            return;
311        }
312        if (sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) {
313            // Restart the timer
314            startFadeTimer();
315        } else {
316            fade(true);
317        }
318    }
319
320    /**
321     * Fades out the playback overlay immediately.
322     */
323    public void fadeOut() {
324        sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
325        fade(false);
326    }
327
328    /**
329     * Sets the {@link PlaybackGlueHost.HostCallback}. Implementor of this interface will
330     * take appropriate actions to take action when the hosting fragment starts/stops processing.
331     */
332    void setHostCallback(PlaybackGlueHost.HostCallback hostCallback) {
333        this.mHostCallback = hostCallback;
334    }
335
336    @Override
337    public void onStop() {
338        if (mHostCallback != null) {
339            mHostCallback.onHostStop();
340        }
341        super.onStop();
342    }
343
344    @Override
345    public void onPause() {
346        if (mHostCallback != null) {
347            mHostCallback.onHostPause();
348        }
349        super.onPause();
350    }
351
352    private boolean areControlsHidden() {
353        return mFadingStatus == IDLE && mBgAlpha == 0;
354    }
355
356    boolean onInterceptInputEvent(InputEvent event) {
357        final boolean controlsHidden = areControlsHidden();
358        if (DEBUG) Log.v(TAG, "onInterceptInputEvent hidden " + controlsHidden + " " + event);
359        boolean consumeEvent = false;
360        int keyCode = KeyEvent.KEYCODE_UNKNOWN;
361
362        if (mInputEventHandler != null) {
363            consumeEvent = mInputEventHandler.handleInputEvent(event);
364        }
365        if (event instanceof KeyEvent) {
366            keyCode = ((KeyEvent) event).getKeyCode();
367        }
368
369        switch (keyCode) {
370            case KeyEvent.KEYCODE_DPAD_CENTER:
371            case KeyEvent.KEYCODE_DPAD_DOWN:
372            case KeyEvent.KEYCODE_DPAD_UP:
373            case KeyEvent.KEYCODE_DPAD_LEFT:
374            case KeyEvent.KEYCODE_DPAD_RIGHT:
375                // Event may be consumed; regardless, if controls are hidden then these keys will
376                // bring up the controls.
377                if (controlsHidden) {
378                    consumeEvent = true;
379                }
380                tickle();
381                break;
382            case KeyEvent.KEYCODE_BACK:
383            case KeyEvent.KEYCODE_ESCAPE:
384                // If fading enabled and controls are not hidden, back will be consumed to fade
385                // them out (even if the key was consumed by the handler).
386                if (mFadingEnabled && !controlsHidden) {
387                    consumeEvent = true;
388                    sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
389                    fade(false);
390                } else if (consumeEvent) {
391                    tickle();
392                }
393                break;
394            default:
395                if (consumeEvent) {
396                    tickle();
397                }
398        }
399        return consumeEvent;
400    }
401
402    @Override
403    public void onResume() {
404        super.onResume();
405        if (mFadingEnabled) {
406            setBgAlpha(0);
407            fade(true);
408        }
409        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
410        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
411        if (mHostCallback != null) {
412            mHostCallback.onHostResume();
413        }
414    }
415
416    void startFadeTimer() {
417        sHandler.removeMessages(START_FADE_OUT, mFragmentReference);
418        sHandler.sendMessageDelayed(sHandler.obtainMessage(START_FADE_OUT, mFragmentReference),
419                mShowTimeMs);
420    }
421
422    private static ValueAnimator loadAnimator(Context context, int resId) {
423        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
424        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
425        return animator;
426    }
427
428    private void loadBgAnimator() {
429        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
430            @Override
431            public void onAnimationUpdate(ValueAnimator arg0) {
432                setBgAlpha((Integer) arg0.getAnimatedValue());
433            }
434        };
435
436        Context context = FragmentUtil.getContext(this);
437        mBgFadeInAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_in);
438        mBgFadeInAnimator.addUpdateListener(listener);
439        mBgFadeInAnimator.addListener(mFadeListener);
440
441        mBgFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_bg_fade_out);
442        mBgFadeOutAnimator.addUpdateListener(listener);
443        mBgFadeOutAnimator.addListener(mFadeListener);
444    }
445
446    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
447    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
448
449    View getControlRowView() {
450        if (getVerticalGridView() == null) {
451            return null;
452        }
453        RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
454        if (vh == null) {
455            return null;
456        }
457        return vh.itemView;
458    }
459
460    private void loadControlRowAnimator() {
461        final AnimatorListener listener = new AnimatorListener() {
462            @Override
463            void getViews(ArrayList<View> views) {
464                View view = getControlRowView();
465                if (view != null) {
466                    views.add(view);
467                }
468            }
469        };
470        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
471            @Override
472            public void onAnimationUpdate(ValueAnimator arg0) {
473                View view = getControlRowView();
474                if (view != null) {
475                    final float fraction = (Float) arg0.getAnimatedValue();
476                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
477                    view.setAlpha(fraction);
478                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
479                }
480            }
481        };
482
483        Context context = FragmentUtil.getContext(this);
484        mControlRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
485        mControlRowFadeInAnimator.addUpdateListener(updateListener);
486        mControlRowFadeInAnimator.addListener(listener);
487        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
488
489        mControlRowFadeOutAnimator = loadAnimator(context,
490                R.animator.lb_playback_controls_fade_out);
491        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
492        mControlRowFadeOutAnimator.addListener(listener);
493        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
494    }
495
496    private void loadOtherRowAnimator() {
497        final AnimatorListener listener = new AnimatorListener() {
498            @Override
499            void getViews(ArrayList<View> views) {
500                if (getVerticalGridView() == null) {
501                    return;
502                }
503                final int count = getVerticalGridView().getChildCount();
504                for (int i = 0; i < count; i++) {
505                    View view = getVerticalGridView().getChildAt(i);
506                    if (view != null) {
507                        views.add(view);
508                    }
509                }
510            }
511        };
512        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
513            @Override
514            public void onAnimationUpdate(ValueAnimator arg0) {
515                if (getVerticalGridView() == null) {
516                    return;
517                }
518                final float fraction = (Float) arg0.getAnimatedValue();
519                for (View view : listener.mViews) {
520                    if (getVerticalGridView().getChildPosition(view) > 0) {
521                        view.setAlpha(fraction);
522                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
523                    }
524                }
525            }
526        };
527
528        Context context = FragmentUtil.getContext(this);
529        mOtherRowFadeInAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_in);
530        mOtherRowFadeInAnimator.addListener(listener);
531        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
532        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
533
534        mOtherRowFadeOutAnimator = loadAnimator(context, R.animator.lb_playback_controls_fade_out);
535        mOtherRowFadeOutAnimator.addListener(listener);
536        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
537        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
538    }
539
540    private void loadDescriptionAnimator() {
541        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
542            @Override
543            public void onAnimationUpdate(ValueAnimator arg0) {
544                if (getVerticalGridView() == null) {
545                    return;
546                }
547                ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
548                        getVerticalGridView().findViewHolderForPosition(0);
549                if (adapterVh != null && adapterVh.getViewHolder()
550                        instanceof PlaybackControlsRowPresenter.ViewHolder) {
551                    final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
552                            adapterVh.getViewHolder()).mDescriptionViewHolder;
553                    if (vh != null) {
554                        vh.view.setAlpha((Float) arg0.getAnimatedValue());
555                    }
556                }
557            }
558        };
559
560        Context context = FragmentUtil.getContext(this);
561        mDescriptionFadeInAnimator = loadAnimator(context,
562                R.animator.lb_playback_description_fade_in);
563        mDescriptionFadeInAnimator.addUpdateListener(listener);
564        mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
565
566        mDescriptionFadeOutAnimator = loadAnimator(context,
567                R.animator.lb_playback_description_fade_out);
568        mDescriptionFadeOutAnimator.addUpdateListener(listener);
569    }
570
571    void fade(boolean fadeIn) {
572        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
573        if (getView() == null) {
574            return;
575        }
576        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
577            if (DEBUG) Log.v(TAG, "requested fade in progress");
578            return;
579        }
580        if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
581            if (DEBUG) Log.v(TAG, "fade is no-op");
582            return;
583        }
584
585        mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0
586                ? mMajorFadeTranslateY : mMinorFadeTranslateY;
587
588        if (mFadingStatus == IDLE) {
589            if (fadeIn) {
590                mBgFadeInAnimator.start();
591                mControlRowFadeInAnimator.start();
592                mOtherRowFadeInAnimator.start();
593                mDescriptionFadeInAnimator.start();
594            } else {
595                mBgFadeOutAnimator.start();
596                mControlRowFadeOutAnimator.start();
597                mOtherRowFadeOutAnimator.start();
598                mDescriptionFadeOutAnimator.start();
599            }
600        } else {
601            if (fadeIn) {
602                mBgFadeOutAnimator.reverse();
603                mControlRowFadeOutAnimator.reverse();
604                mOtherRowFadeOutAnimator.reverse();
605                mDescriptionFadeOutAnimator.reverse();
606            } else {
607                mBgFadeInAnimator.reverse();
608                mControlRowFadeInAnimator.reverse();
609                mOtherRowFadeInAnimator.reverse();
610                mDescriptionFadeInAnimator.reverse();
611            }
612        }
613
614        // If fading in while control row is focused, set initial translationY so
615        // views slide in from below.
616        if (fadeIn && mFadingStatus == IDLE) {
617            final int count = getVerticalGridView().getChildCount();
618            for (int i = 0; i < count; i++) {
619                getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
620            }
621        }
622
623        mFadingStatus = fadeIn ? IN : OUT;
624    }
625
626    /**
627     * Sets the list of rows for the fragment.
628     */
629    @Override
630    public void setAdapter(ObjectAdapter adapter) {
631        if (getAdapter() != null) {
632            getAdapter().unregisterObserver(mObserver);
633        }
634        super.setAdapter(adapter);
635        if (adapter != null) {
636            adapter.registerObserver(mObserver);
637        }
638    }
639
640    @Override
641    void setVerticalGridViewLayout(VerticalGridView listview) {
642        if (listview == null) {
643            return;
644        }
645        // Padding affects alignment when last row is focused
646        // (last is first when there's only one row).
647        setPadding(listview, mPaddingTop, mPaddingBottom);
648
649        // Item alignment affects focused row that isn't the last.
650        listview.setItemAlignmentOffset(0);
651        listview.setItemAlignmentOffsetPercent(50);
652
653        // Push rows to the bottom.
654        listview.setWindowAlignmentOffset(0);
655        listview.setWindowAlignmentOffsetPercent(50);
656        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
657    }
658
659    private static void setPadding(View view, int paddingTop, int paddingBottom) {
660        view.setPadding(view.getPaddingLeft(), paddingTop,
661                view.getPaddingRight(), paddingBottom);
662    }
663
664    @Override
665    public void onCreate(Bundle savedInstanceState) {
666        super.onCreate(savedInstanceState);
667
668        mPaddingTop =
669                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_top);
670        mPaddingBottom =
671                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
672        mBgDarkColor =
673                getResources().getColor(R.color.lb_playback_controls_background_dark);
674        mBgLightColor =
675                getResources().getColor(R.color.lb_playback_controls_background_light);
676        mShowTimeMs =
677                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
678        mMajorFadeTranslateY =
679                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
680        mMinorFadeTranslateY =
681                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
682
683        loadBgAnimator();
684        loadControlRowAnimator();
685        loadOtherRowAnimator();
686        loadDescriptionAnimator();
687    }
688
689    /**
690     * Sets the background type.
691     *
692     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
693     */
694    public void setBackgroundType(int type) {
695        switch (type) {
696        case BG_LIGHT:
697        case BG_DARK:
698        case BG_NONE:
699            if (type != mBackgroundType) {
700                mBackgroundType = type;
701                updateBackground();
702            }
703            break;
704        default:
705            throw new IllegalArgumentException("Invalid background type");
706        }
707    }
708
709    /**
710     * Returns the background type.
711     */
712    public int getBackgroundType() {
713        return mBackgroundType;
714    }
715
716    private void updateBackground() {
717        if (mRootView != null) {
718            int color = mBgDarkColor;
719            switch (mBackgroundType) {
720                case BG_DARK: break;
721                case BG_LIGHT: color = mBgLightColor; break;
722                case BG_NONE: color = Color.TRANSPARENT; break;
723            }
724            mRootView.setBackground(new ColorDrawable(color));
725        }
726    }
727
728    void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
729        // Add extra space between rows 0 and 1
730        if (vh == null && getVerticalGridView() != null) {
731            vh = (ItemBridgeAdapter.ViewHolder)
732                    getVerticalGridView().findViewHolderForPosition(0);
733        }
734        if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
735            final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
736            ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
737                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
738                    adapterSize > 1);
739        }
740    }
741
742    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
743            new ItemBridgeAdapter.AdapterListener() {
744        @Override
745        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
746            if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
747            if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
748                if (DEBUG) Log.v(TAG, "setting alpha to 0");
749                vh.getViewHolder().view.setAlpha(0);
750            }
751            if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
752                resetControlsToPrimaryActions(vh);
753            }
754        }
755        @Override
756        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
757            if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
758            // Reset animation state
759            vh.getViewHolder().view.setAlpha(1f);
760            vh.getViewHolder().view.setTranslationY(0);
761            if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
762                Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
763                        vh.getViewHolder()).mDescriptionViewHolder;
764                if (descriptionVh != null) {
765                    descriptionVh.view.setAlpha(1f);
766                }
767            }
768        }
769        @Override
770        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
771            if (vh.getPosition() == 0) {
772                updateControlsBottomSpace(vh);
773            }
774        }
775    };
776
777    @Override
778    public View onCreateView(LayoutInflater inflater, ViewGroup container,
779            Bundle savedInstanceState) {
780        mRootView = super.onCreateView(inflater, container, savedInstanceState);
781        mBgAlpha = 255;
782        updateBackground();
783        getRowsFragment().setExternalAdapterListener(mAdapterListener);
784        return mRootView;
785    }
786
787    @Override
788    public void onDestroyView() {
789        mRootView = null;
790        super.onDestroyView();
791    }
792
793    @Override
794    public void onStart() {
795        super.onStart();
796        // Workaround problem VideoView forcing itself to focused, let controls take focus.
797        getRowsFragment().getView().requestFocus();
798        if (mHostCallback != null) {
799            mHostCallback.onHostStart();
800        }
801    }
802
803    private final DataObserver mObserver = new DataObserver() {
804        @Override
805        public void onChanged() {
806            updateControlsBottomSpace(null);
807        }
808    };
809
810    static abstract class AnimatorListener implements Animator.AnimatorListener {
811        ArrayList<View> mViews = new ArrayList<View>();
812        ArrayList<Integer> mLayerType = new ArrayList<Integer>();
813
814        @Override
815        public void onAnimationCancel(Animator animation) {
816        }
817        @Override
818        public void onAnimationRepeat(Animator animation) {
819        }
820        @Override
821        public void onAnimationStart(Animator animation) {
822            getViews(mViews);
823            for (View view : mViews) {
824                mLayerType.add(view.getLayerType());
825                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
826            }
827        }
828        @Override
829        public void onAnimationEnd(Animator animation) {
830            for (int i = 0; i < mViews.size(); i++) {
831                mViews.get(i).setLayerType(mLayerType.get(i), null);
832            }
833            mLayerType.clear();
834            mViews.clear();
835        }
836        abstract void getViews(ArrayList<View> views);
837
838    };
839}
840