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