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