PlaybackOverlayFragment.java revision f47fb1e34efd538c322f7539893272ba847cdbdc
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 + " event " + event);
297        boolean consumeEvent = (mFadingStatus == IDLE && mBgAlpha == 0);
298        if (event instanceof KeyEvent) {
299            if (consumeEvent) {
300                consumeEvent = isConsumableKey((KeyEvent) event);
301            }
302            int keyCode = ((KeyEvent) event).getKeyCode();
303            // Back key typically means we're leaving the fragment
304            if (keyCode != KeyEvent.KEYCODE_BACK) {
305                tickle();
306            }
307        } else {
308            tickle();
309        }
310        if (!consumeEvent && mInputEventHandler != null) {
311            consumeEvent = mInputEventHandler.handleInputEvent(event);
312        }
313        return consumeEvent;
314    }
315
316    @Override
317    public void onResume() {
318        super.onResume();
319        if (mFadingEnabled) {
320            setBgAlpha(0);
321            fade(true);
322        }
323        getVerticalGridView().setOnTouchInterceptListener(mOnTouchInterceptListener);
324        getVerticalGridView().setOnMotionInterceptListener(mOnMotionInterceptListener);
325        getVerticalGridView().setOnKeyInterceptListener(mOnKeyInterceptListener);
326    }
327
328    private void startFadeTimer() {
329        if (mHandler != null) {
330            mHandler.removeMessages(START_FADE_OUT);
331            mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs);
332        }
333    }
334
335    private static ValueAnimator loadAnimator(Context context, int resId) {
336        ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(context, resId);
337        animator.setDuration(animator.getDuration() * ANIMATION_MULTIPLIER);
338        return animator;
339    }
340
341    private void loadBgAnimator() {
342        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
343            @Override
344            public void onAnimationUpdate(ValueAnimator arg0) {
345                setBgAlpha((Integer) arg0.getAnimatedValue());
346            }
347        };
348
349        mBgFadeInAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_in);
350        mBgFadeInAnimator.addUpdateListener(listener);
351        mBgFadeInAnimator.addListener(mFadeListener);
352
353        mBgFadeOutAnimator = loadAnimator(getActivity(), R.animator.lb_playback_bg_fade_out);
354        mBgFadeOutAnimator.addUpdateListener(listener);
355        mBgFadeOutAnimator.addListener(mFadeListener);
356    }
357
358    private TimeInterpolator mLogDecelerateInterpolator = new LogDecelerateInterpolator(100,0);
359    private TimeInterpolator mLogAccelerateInterpolator = new LogAccelerateInterpolator(100,0);
360
361    private View getControlRowView() {
362        if (getVerticalGridView() == null) {
363            return null;
364        }
365        RecyclerView.ViewHolder vh = getVerticalGridView().findViewHolderForPosition(0);
366        if (vh == null) {
367            return null;
368        }
369        return vh.itemView;
370    }
371
372    private void loadControlRowAnimator() {
373        final AnimatorListener listener = new AnimatorListener() {
374            @Override
375            void getViews(ArrayList<View> views) {
376                View view = getControlRowView();
377                if (view != null) {
378                    views.add(view);
379                }
380            }
381        };
382        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
383            @Override
384            public void onAnimationUpdate(ValueAnimator arg0) {
385                View view = getControlRowView();
386                if (view != null) {
387                    final float fraction = (Float) arg0.getAnimatedValue();
388                    if (DEBUG) Log.v(TAG, "fraction " + fraction);
389                    view.setAlpha(fraction);
390                    view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
391                }
392            }
393        };
394
395        mControlRowFadeInAnimator = loadAnimator(
396                getActivity(), R.animator.lb_playback_controls_fade_in);
397        mControlRowFadeInAnimator.addUpdateListener(updateListener);
398        mControlRowFadeInAnimator.addListener(listener);
399        mControlRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
400
401        mControlRowFadeOutAnimator = loadAnimator(
402                getActivity(), R.animator.lb_playback_controls_fade_out);
403        mControlRowFadeOutAnimator.addUpdateListener(updateListener);
404        mControlRowFadeOutAnimator.addListener(listener);
405        mControlRowFadeOutAnimator.setInterpolator(mLogAccelerateInterpolator);
406    }
407
408    private void loadOtherRowAnimator() {
409        final AnimatorListener listener = new AnimatorListener() {
410            @Override
411            void getViews(ArrayList<View> views) {
412                if (getVerticalGridView() == null) {
413                    return;
414                }
415                final int count = getVerticalGridView().getChildCount();
416                for (int i = 0; i < count; i++) {
417                    View view = getVerticalGridView().getChildAt(i);
418                    if (view != null) {
419                        views.add(view);
420                    }
421                }
422            }
423        };
424        final AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
425            @Override
426            public void onAnimationUpdate(ValueAnimator arg0) {
427                final float fraction = (Float) arg0.getAnimatedValue();
428                for (View view : listener.mViews) {
429                    if (getVerticalGridView().getChildPosition(view) > 0) {
430                        view.setAlpha(fraction);
431                        view.setTranslationY((float) mAnimationTranslateY * (1f - fraction));
432                    }
433                }
434            }
435        };
436
437        mOtherRowFadeInAnimator = loadAnimator(
438                getActivity(), R.animator.lb_playback_controls_fade_in);
439        mOtherRowFadeInAnimator.addListener(listener);
440        mOtherRowFadeInAnimator.addUpdateListener(updateListener);
441        mOtherRowFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
442
443        mOtherRowFadeOutAnimator = loadAnimator(
444                getActivity(), R.animator.lb_playback_controls_fade_out);
445        mOtherRowFadeOutAnimator.addListener(listener);
446        mOtherRowFadeOutAnimator.addUpdateListener(updateListener);
447        mOtherRowFadeOutAnimator.setInterpolator(new AccelerateInterpolator());
448    }
449
450    private void loadDescriptionAnimator() {
451        AnimatorUpdateListener listener = new AnimatorUpdateListener() {
452            @Override
453            public void onAnimationUpdate(ValueAnimator arg0) {
454                if (getVerticalGridView() == null) {
455                    return;
456                }
457                ItemBridgeAdapter.ViewHolder adapterVh = (ItemBridgeAdapter.ViewHolder)
458                        getVerticalGridView().findViewHolderForPosition(0);
459                if (adapterVh != null && adapterVh.getViewHolder()
460                        instanceof PlaybackControlsRowPresenter.ViewHolder) {
461                    final Presenter.ViewHolder vh = ((PlaybackControlsRowPresenter.ViewHolder)
462                            adapterVh.getViewHolder()).mDescriptionViewHolder;
463                    if (vh != null) {
464                        vh.view.setAlpha((Float) arg0.getAnimatedValue());
465                    }
466                }
467            }
468        };
469
470        mDescriptionFadeInAnimator = loadAnimator(
471                getActivity(), R.animator.lb_playback_description_fade_in);
472        mDescriptionFadeInAnimator.addUpdateListener(listener);
473        mDescriptionFadeInAnimator.setInterpolator(mLogDecelerateInterpolator);
474
475        mDescriptionFadeOutAnimator = loadAnimator(
476                getActivity(), R.animator.lb_playback_description_fade_out);
477        mDescriptionFadeOutAnimator.addUpdateListener(listener);
478    }
479
480    private void fade(boolean fadeIn) {
481        if (DEBUG) Log.v(TAG, "fade " + fadeIn);
482        if (getView() == null) {
483            return;
484        }
485        if ((fadeIn && mFadingStatus == IN) || (!fadeIn && mFadingStatus == OUT)) {
486            if (DEBUG) Log.v(TAG, "requested fade in progress");
487            return;
488        }
489        if ((fadeIn && mBgAlpha == 255) || (!fadeIn && mBgAlpha == 0)) {
490            if (DEBUG) Log.v(TAG, "fade is no-op");
491            return;
492        }
493
494        mAnimationTranslateY = getVerticalGridView().getSelectedPosition() == 0 ?
495                mMajorFadeTranslateY : mMinorFadeTranslateY;
496
497        if (mFadingStatus == IDLE) {
498            if (fadeIn) {
499                mBgFadeInAnimator.start();
500                mControlRowFadeInAnimator.start();
501                mOtherRowFadeInAnimator.start();
502                mDescriptionFadeInAnimator.start();
503            } else {
504                mBgFadeOutAnimator.start();
505                mControlRowFadeOutAnimator.start();
506                mOtherRowFadeOutAnimator.start();
507                mDescriptionFadeOutAnimator.start();
508            }
509        } else {
510            if (fadeIn) {
511                mBgFadeOutAnimator.reverse();
512                mControlRowFadeOutAnimator.reverse();
513                mOtherRowFadeOutAnimator.reverse();
514                mDescriptionFadeOutAnimator.reverse();
515            } else {
516                mBgFadeInAnimator.reverse();
517                mControlRowFadeInAnimator.reverse();
518                mOtherRowFadeInAnimator.reverse();
519                mDescriptionFadeInAnimator.reverse();
520            }
521        }
522
523        // If fading in while control row is focused, set initial translationY so
524        // views slide in from below.
525        if (fadeIn && mFadingStatus == IDLE) {
526            final int count = getVerticalGridView().getChildCount();
527            for (int i = 0; i < count; i++) {
528                getVerticalGridView().getChildAt(i).setTranslationY(mAnimationTranslateY);
529            }
530        }
531
532        mFadingStatus = fadeIn ? IN : OUT;
533    }
534
535    /**
536     * Sets the list of rows for the fragment.
537     */
538    @Override
539    public void setAdapter(ObjectAdapter adapter) {
540        if (getAdapter() != null) {
541            getAdapter().unregisterObserver(mObserver);
542        }
543        super.setAdapter(adapter);
544        if (adapter != null) {
545            adapter.registerObserver(mObserver);
546        }
547    }
548
549    @Override
550    void setVerticalGridViewLayout(VerticalGridView listview) {
551        if (listview == null) {
552            return;
553        }
554        // Padding affects alignment when last row is focused
555        // (last is first when there's only one row).
556        setBottomPadding(listview, mPaddingBottom);
557
558        // Item alignment affects focused row that isn't the last.
559        listview.setItemAlignmentOffset(mAlignPosition);
560        listview.setItemAlignmentOffsetPercent(100);
561
562        // Push rows to the bottom.
563        listview.setWindowAlignmentOffset(0);
564        listview.setWindowAlignmentOffsetPercent(100);
565        listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
566    }
567
568    private static void setBottomPadding(View view, int padding) {
569        view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
570                view.getPaddingRight(), padding);
571    }
572
573    @Override
574    public void onCreate(Bundle savedInstanceState) {
575        super.onCreate(savedInstanceState);
576
577        mAlignPosition =
578                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
579        mPaddingBottom =
580                getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
581        mBgDarkColor =
582                getResources().getColor(R.color.lb_playback_controls_background_dark);
583        mBgLightColor =
584                getResources().getColor(R.color.lb_playback_controls_background_light);
585        mShowTimeMs =
586                getResources().getInteger(R.integer.lb_playback_controls_show_time_ms);
587        mMajorFadeTranslateY =
588                getResources().getDimensionPixelSize(R.dimen.lb_playback_major_fade_translate_y);
589        mMinorFadeTranslateY =
590                getResources().getDimensionPixelSize(R.dimen.lb_playback_minor_fade_translate_y);
591
592        loadBgAnimator();
593        loadControlRowAnimator();
594        loadOtherRowAnimator();
595        loadDescriptionAnimator();
596    }
597
598    /**
599     * Sets the background type.
600     *
601     * @param type One of BG_LIGHT, BG_DARK, or BG_NONE.
602     */
603    public void setBackgroundType(int type) {
604        switch (type) {
605        case BG_LIGHT:
606        case BG_DARK:
607        case BG_NONE:
608            if (type != mBackgroundType) {
609                mBackgroundType = type;
610                updateBackground();
611            }
612            break;
613        default:
614            throw new IllegalArgumentException("Invalid background type");
615        }
616    }
617
618    /**
619     * Returns the background type.
620     */
621    public int getBackgroundType() {
622        return mBackgroundType;
623    }
624
625    private void updateBackground() {
626        if (mRootView != null) {
627            int color = mBgDarkColor;
628            switch (mBackgroundType) {
629                case BG_DARK: break;
630                case BG_LIGHT: color = mBgLightColor; break;
631                case BG_NONE: color = Color.TRANSPARENT; break;
632            }
633            mRootView.setBackground(new ColorDrawable(color));
634        }
635    }
636
637    private void updateControlsBottomSpace(ItemBridgeAdapter.ViewHolder vh) {
638        // Add extra space between rows 0 and 1
639        if (vh == null && getVerticalGridView() != null) {
640            vh = (ItemBridgeAdapter.ViewHolder)
641                    getVerticalGridView().findViewHolderForPosition(0);
642        }
643        if (vh != null && vh.getPresenter() instanceof PlaybackControlsRowPresenter) {
644            final int adapterSize = getAdapter() == null ? 0 : getAdapter().size();
645            ((PlaybackControlsRowPresenter) vh.getPresenter()).showBottomSpace(
646                    (PlaybackControlsRowPresenter.ViewHolder) vh.getViewHolder(),
647                    adapterSize > 1);
648        }
649    }
650
651    private final ItemBridgeAdapter.AdapterListener mAdapterListener =
652            new ItemBridgeAdapter.AdapterListener() {
653        @Override
654        public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
655            if (DEBUG) Log.v(TAG, "onAttachedToWindow " + vh.getViewHolder().view);
656            if ((mFadingStatus == IDLE && mBgAlpha == 0) || mFadingStatus == OUT) {
657                if (DEBUG) Log.v(TAG, "setting alpha to 0");
658                vh.getViewHolder().view.setAlpha(0);
659            }
660            if (vh.getPosition() == 0 && mResetControlsToPrimaryActionsPending) {
661                resetControlsToPrimaryActions(vh);
662            }
663        }
664        @Override
665        public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
666            if (DEBUG) Log.v(TAG, "onDetachedFromWindow " + vh.getViewHolder().view);
667            // Reset animation state
668            vh.getViewHolder().view.setAlpha(1f);
669            vh.getViewHolder().view.setTranslationY(0);
670            if (vh.getViewHolder() instanceof PlaybackControlsRowPresenter.ViewHolder) {
671                Presenter.ViewHolder descriptionVh = ((PlaybackControlsRowPresenter.ViewHolder)
672                        vh.getViewHolder()).mDescriptionViewHolder;
673                if (descriptionVh != null) {
674                    descriptionVh.view.setAlpha(1f);
675                }
676            }
677        }
678        @Override
679        public void onBind(ItemBridgeAdapter.ViewHolder vh) {
680            if (vh.getPosition() == 0) {
681                updateControlsBottomSpace(vh);
682            }
683        }
684    };
685
686    @Override
687    public View onCreateView(LayoutInflater inflater, ViewGroup container,
688            Bundle savedInstanceState) {
689        mRootView = super.onCreateView(inflater, container, savedInstanceState);
690        mBgAlpha = 255;
691        updateBackground();
692        getRowsFragment().setExternalAdapterListener(mAdapterListener);
693        return mRootView;
694    }
695
696    @Override
697    public void onDestroyView() {
698        mRootView = null;
699        super.onDestroyView();
700    }
701
702    private final DataObserver mObserver = new DataObserver() {
703        public void onChanged() {
704            updateControlsBottomSpace(null);
705        }
706    };
707
708    static abstract class AnimatorListener implements Animator.AnimatorListener {
709        ArrayList<View> mViews = new ArrayList<View>();
710        ArrayList<Integer> mLayerType = new ArrayList<Integer>();
711
712        public void onAnimationCancel(Animator animation) {
713        }
714        public void onAnimationRepeat(Animator animation) {
715        }
716        public void onAnimationStart(Animator animation) {
717            getViews(mViews);
718            for (View view : mViews) {
719                mLayerType.add(view.getLayerType());
720                view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
721            }
722        }
723        public void onAnimationEnd(Animator animation) {
724            for (int i = 0; i < mViews.size(); i++) {
725                mViews.get(i).setLayerType(mLayerType.get(i), null);
726            }
727            mLayerType.clear();
728            mViews.clear();
729        }
730        abstract void getViews(ArrayList<View> views);
731    };
732}
733