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