1// CHECKSTYLE:OFF Generated code
2/* This file is auto-generated from {}DetailsSupportFragmentBackgroundController.java.  DO NOT MODIFY. */
3
4/*
5 * Copyright (C) 2017 The Android Open Source Project
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19package android.support.v17.leanback.app;
20
21import android.animation.PropertyValuesHolder;
22import android.graphics.Bitmap;
23import android.graphics.Color;
24import android.graphics.drawable.ColorDrawable;
25import android.graphics.drawable.Drawable;
26import android.support.annotation.ColorInt;
27import android.support.annotation.NonNull;
28import android.support.annotation.Nullable;
29import android.support.v17.leanback.R;
30import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
31import android.support.v17.leanback.media.PlaybackGlue;
32import android.support.v17.leanback.media.PlaybackGlueHost;
33import android.support.v17.leanback.widget.DetailsParallaxDrawable;
34import android.support.v17.leanback.widget.ParallaxTarget;
35import android.app.Fragment;
36
37/**
38 * Controller for DetailsFragment parallax background and embedded video play.
39 * <p>
40 * The parallax background drawable is made of two parts: cover drawable (by default
41 * {@link FitWidthBitmapDrawable}) above the details overview row and bottom drawable (by default
42 * {@link ColorDrawable}) below the details overview row. While vertically scrolling rows, the size
43 * of cover drawable and bottom drawable will be updated and the cover drawable will by default
44 * perform a parallax shift using {@link FitWidthBitmapDrawable#PROPERTY_VERTICAL_OFFSET}.
45 * </p>
46 * <pre>
47 *        ***************************
48 *        *      Cover Drawable     *
49 *        * (FitWidthBitmapDrawable)*
50 *        *                         *
51 *        ***************************
52 *        *    DetailsOverviewRow   *
53 *        *                         *
54 *        ***************************
55 *        *     Bottom Drawable     *
56 *        *      (ColorDrawable)    *
57 *        *         Related         *
58 *        *         Content         *
59 *        ***************************
60 * </pre>
61 * Both parallax background drawable and embedded video play are optional. App must call
62 * {@link #enableParallax()} and/or {@link #setupVideoPlayback(PlaybackGlue)} explicitly.
63 * The PlaybackGlue is automatically {@link PlaybackGlue#play()} when fragment starts and
64 * {@link PlaybackGlue#pause()} when fragment stops. When video is ready to play, cover drawable
65 * will be faded out.
66 * Example:
67 * <pre>
68 * DetailsFragmentBackgroundController mController = new DetailsFragmentBackgroundController(this);
69 *
70 * public void onCreate(Bundle savedInstance) {
71 *     super.onCreate(savedInstance);
72 *     MediaPlayerGlue player = new MediaPlayerGlue(..);
73 *     player.setUrl(...);
74 *     mController.enableParallax();
75 *     mController.setupVideoPlayback(player);
76 * }
77 *
78 * static class MyLoadBitmapTask extends ... {
79 *     WeakReference<MyFragment> mFragmentRef;
80 *     MyLoadBitmapTask(MyFragment fragment) {
81 *         mFragmentRef = new WeakReference(fragment);
82 *     }
83 *     protected void onPostExecute(Bitmap bitmap) {
84 *         MyFragment fragment = mFragmentRef.get();
85 *         if (fragment != null) {
86 *             fragment.mController.setCoverBitmap(bitmap);
87 *         }
88 *     }
89 * }
90 *
91 * public void onStart() {
92 *     new MyLoadBitmapTask(this).execute(url);
93 * }
94 *
95 * public void onStop() {
96 *     mController.setCoverBitmap(null);
97 * }
98 * </pre>
99 * <p>
100 * To customize cover drawable and/or bottom drawable, app should call
101 * {@link #enableParallax(Drawable, Drawable, ParallaxTarget.PropertyValuesHolderTarget)}.
102 * If app supplies a custom cover Drawable, it should not call {@link #setCoverBitmap(Bitmap)}.
103 * If app supplies a custom bottom Drawable, it should not call {@link #setSolidColor(int)}.
104 * </p>
105 * <p>
106 * To customize playback fragment, app should override {@link #onCreateVideoFragment()} and
107 * {@link #onCreateGlueHost()}.
108 * </p>
109 *
110 */
111public class DetailsFragmentBackgroundController {
112
113    final DetailsFragment mFragment;
114    DetailsParallaxDrawable mParallaxDrawable;
115    int mParallaxDrawableMaxOffset;
116    PlaybackGlue mPlaybackGlue;
117    DetailsBackgroundVideoHelper mVideoHelper;
118    Bitmap mCoverBitmap;
119    int mSolidColor;
120    boolean mCanUseHost = false;
121    boolean mInitialControlVisible = false;
122
123    private Fragment mLastVideoFragmentForGlueHost;
124
125    /**
126     * Creates a DetailsFragmentBackgroundController for a DetailsFragment. Note that
127     * each DetailsFragment can only associate with one DetailsFragmentBackgroundController.
128     *
129     * @param fragment The DetailsFragment to control background and embedded video playing.
130     * @throws IllegalStateException If fragment was already associated with another controller.
131     */
132    public DetailsFragmentBackgroundController(DetailsFragment fragment) {
133        if (fragment.mDetailsBackgroundController != null) {
134            throw new IllegalStateException("Each DetailsFragment is allowed to initialize "
135                    + "DetailsFragmentBackgroundController once");
136        }
137        fragment.mDetailsBackgroundController = this;
138        mFragment = fragment;
139    }
140
141    /**
142     * Enables default parallax background using a {@link FitWidthBitmapDrawable} as cover drawable
143     * and {@link ColorDrawable} as bottom drawable. A vertical parallax movement will be applied
144     * to the FitWidthBitmapDrawable. App may use {@link #setSolidColor(int)} and
145     * {@link #setCoverBitmap(Bitmap)} to change the content of bottom drawable and cover drawable.
146     * This method must be called before {@link #setupVideoPlayback(PlaybackGlue)}.
147     *
148     * @see #setCoverBitmap(Bitmap)
149     * @see #setSolidColor(int)
150     * @throws IllegalStateException If {@link #setupVideoPlayback(PlaybackGlue)} was called.
151     */
152    public void enableParallax() {
153        int offset = mParallaxDrawableMaxOffset;
154        if (offset == 0) {
155            offset = FragmentUtil.getContext(mFragment).getResources()
156                    .getDimensionPixelSize(R.dimen.lb_details_cover_drawable_parallax_movement);
157        }
158        Drawable coverDrawable = new FitWidthBitmapDrawable();
159        ColorDrawable colorDrawable = new ColorDrawable();
160        enableParallax(coverDrawable, colorDrawable,
161                new ParallaxTarget.PropertyValuesHolderTarget(
162                        coverDrawable,
163                        PropertyValuesHolder.ofInt(FitWidthBitmapDrawable.PROPERTY_VERTICAL_OFFSET,
164                                0, -offset)
165                ));
166    }
167
168    /**
169     * Enables parallax background using a custom cover drawable at top and a custom bottom
170     * drawable. This method must be called before {@link #setupVideoPlayback(PlaybackGlue)}.
171     *
172     * @param coverDrawable Custom cover drawable shown at top. {@link #setCoverBitmap(Bitmap)}
173     *                      will not work if coverDrawable is not {@link FitWidthBitmapDrawable};
174     *                      in that case it's app's responsibility to set content into
175     *                      coverDrawable.
176     * @param bottomDrawable Drawable shown at bottom. {@link #setSolidColor(int)} will not work
177     *                       if bottomDrawable is not {@link ColorDrawable}; in that case it's app's
178     *                       responsibility to set content of bottomDrawable.
179     * @param coverDrawableParallaxTarget Target to perform parallax effect within coverDrawable.
180     *                                    Use null for no parallax movement effect.
181     *                                    Example to move bitmap within FitWidthBitmapDrawable:
182     *                                    new ParallaxTarget.PropertyValuesHolderTarget(
183     *                                        coverDrawable, PropertyValuesHolder.ofInt(
184     *                                            FitWidthBitmapDrawable.PROPERTY_VERTICAL_OFFSET,
185     *                                            0, -120))
186     * @throws IllegalStateException If {@link #setupVideoPlayback(PlaybackGlue)} was called.
187     */
188    public void enableParallax(@NonNull Drawable coverDrawable, @NonNull Drawable bottomDrawable,
189                               @Nullable ParallaxTarget.PropertyValuesHolderTarget
190                                       coverDrawableParallaxTarget) {
191        if (mParallaxDrawable != null) {
192            return;
193        }
194        // if bitmap is set before enableParallax, use it as initial value.
195        if (mCoverBitmap != null && coverDrawable instanceof FitWidthBitmapDrawable) {
196            ((FitWidthBitmapDrawable) coverDrawable).setBitmap(mCoverBitmap);
197        }
198        // if solid color is set before enableParallax, use it as initial value.
199        if (mSolidColor != Color.TRANSPARENT && bottomDrawable instanceof ColorDrawable) {
200            ((ColorDrawable) bottomDrawable).setColor(mSolidColor);
201        }
202        if (mPlaybackGlue != null) {
203            throw new IllegalStateException("enableParallaxDrawable must be called before "
204                    + "enableVideoPlayback");
205        }
206        mParallaxDrawable = new DetailsParallaxDrawable(
207                FragmentUtil.getContext(mFragment),
208                mFragment.getParallax(),
209                coverDrawable,
210                bottomDrawable,
211                coverDrawableParallaxTarget);
212        mFragment.setBackgroundDrawable(mParallaxDrawable);
213        // create a VideoHelper with null PlaybackGlue for changing CoverDrawable visibility
214        // before PlaybackGlue is ready.
215        mVideoHelper = new DetailsBackgroundVideoHelper(null,
216                mFragment.getParallax(), mParallaxDrawable.getCoverDrawable());
217    }
218
219    /**
220     * Enable video playback and set proper {@link PlaybackGlueHost}. This method by default
221     * creates a VideoFragment and VideoFragmentGlueHost to host the PlaybackGlue.
222     * This method must be called after calling details Fragment super.onCreate(). This method
223     * can be called multiple times to replace existing PlaybackGlue or calling
224     * setupVideoPlayback(null) to clear. Note a typical {@link PlaybackGlue} subclass releases
225     * resources in {@link PlaybackGlue#onDetachedFromHost()}, when the {@link PlaybackGlue}
226     * subclass is not doing that, it's app's responsibility to release the resources.
227     *
228     * @param playbackGlue The new PlaybackGlue to set as background or null to clear existing one.
229     * @see #onCreateVideoFragment()
230     * @see #onCreateGlueHost().
231     */
232    @SuppressWarnings("ReferenceEquality")
233    public void setupVideoPlayback(@NonNull PlaybackGlue playbackGlue) {
234        if (mPlaybackGlue == playbackGlue) {
235            return;
236        }
237
238        PlaybackGlueHost playbackGlueHost = null;
239        if (mPlaybackGlue != null) {
240            playbackGlueHost = mPlaybackGlue.getHost();
241            mPlaybackGlue.setHost(null);
242        }
243
244        mPlaybackGlue = playbackGlue;
245        mVideoHelper.setPlaybackGlue(mPlaybackGlue);
246        if (mCanUseHost && mPlaybackGlue != null) {
247            if (playbackGlueHost == null
248                    || mLastVideoFragmentForGlueHost != findOrCreateVideoFragment()) {
249                mPlaybackGlue.setHost(createGlueHost());
250                mLastVideoFragmentForGlueHost = findOrCreateVideoFragment();
251            } else {
252                mPlaybackGlue.setHost(playbackGlueHost);
253            }
254        }
255    }
256
257    /**
258     * Returns current PlaybackGlue or null if not set or cleared.
259     *
260     * @return Current PlaybackGlue or null
261     */
262    public final PlaybackGlue getPlaybackGlue() {
263        return mPlaybackGlue;
264    }
265
266    /**
267     * Precondition allows user navigate to video fragment using DPAD. Default implementation
268     * returns true if PlaybackGlue is not null. Subclass may override, e.g. only allow navigation
269     * when {@link PlaybackGlue#isPrepared()} is true. Note this method does not block
270     * app calls {@link #switchToVideo}.
271     *
272     * @return True allow to navigate to video fragment.
273     */
274    public boolean canNavigateToVideoFragment() {
275        return mPlaybackGlue != null;
276    }
277
278    void switchToVideoBeforeCreate() {
279        mVideoHelper.crossFadeBackgroundToVideo(true, true);
280        mInitialControlVisible = true;
281    }
282
283    /**
284     * Switch to video fragment, note that this method is not affected by result of
285     * {@link #canNavigateToVideoFragment()}. If the method is called in DetailsFragment.onCreate()
286     * it will make video fragment to be initially focused once it is created.
287     * <p>
288     * Calling switchToVideo() in DetailsFragment.onCreate() will clear the activity enter
289     * transition and shared element transition.
290     * </p>
291     * <p>
292     * If switchToVideo() is called after {@link DetailsFragment#prepareEntranceTransition()} and
293     * before {@link DetailsFragment#onEntranceTransitionEnd()}, it will be ignored.
294     * </p>
295     * <p>
296     * If {@link DetailsFragment#prepareEntranceTransition()} is called after switchToVideo(), an
297     * IllegalStateException will be thrown.
298     * </p>
299     */
300    public final void switchToVideo() {
301        mFragment.switchToVideo();
302    }
303
304    /**
305     * Switch to rows fragment.
306     */
307    public final void switchToRows() {
308        mFragment.switchToRows();
309    }
310
311    /**
312     * When fragment is started and no running transition. First set host if not yet set, second
313     * start playing if it was paused before.
314     */
315    void onStart() {
316        if (!mCanUseHost) {
317            mCanUseHost = true;
318            if (mPlaybackGlue != null) {
319                mPlaybackGlue.setHost(createGlueHost());
320                mLastVideoFragmentForGlueHost = findOrCreateVideoFragment();
321            }
322        }
323        if (mPlaybackGlue != null && mPlaybackGlue.isPrepared()) {
324            mPlaybackGlue.play();
325        }
326    }
327
328    void onStop() {
329        if (mPlaybackGlue != null) {
330            mPlaybackGlue.pause();
331        }
332    }
333
334    /**
335     * Disable parallax that would auto-start video playback
336     * @return true if video fragment is visible or false otherwise.
337     */
338    boolean disableVideoParallax() {
339        if (mVideoHelper != null) {
340            mVideoHelper.stopParallax();
341            return mVideoHelper.isVideoVisible();
342        }
343        return false;
344    }
345
346    /**
347     * Returns the cover drawable at top. Returns null if {@link #enableParallax()} is not called.
348     * By default it's a {@link FitWidthBitmapDrawable}.
349     *
350     * @return The cover drawable at top.
351     */
352    public final Drawable getCoverDrawable() {
353        if (mParallaxDrawable == null) {
354            return null;
355        }
356        return mParallaxDrawable.getCoverDrawable();
357    }
358
359    /**
360     * Returns the drawable at bottom. Returns null if {@link #enableParallax()} is not called.
361     * By default it's a {@link ColorDrawable}.
362     *
363     * @return The bottom drawable.
364     */
365    public final Drawable getBottomDrawable() {
366        if (mParallaxDrawable == null) {
367            return null;
368        }
369        return mParallaxDrawable.getBottomDrawable();
370    }
371
372    /**
373     * Creates a Fragment to host {@link PlaybackGlue}. Returns a new {@link VideoFragment} by
374     * default. App may override and return a different fragment and it also must override
375     * {@link #onCreateGlueHost()}.
376     *
377     * @return A new fragment used in {@link #onCreateGlueHost()}.
378     * @see #onCreateGlueHost()
379     * @see #setupVideoPlayback(PlaybackGlue)
380     */
381    public Fragment onCreateVideoFragment() {
382        return new VideoFragment();
383    }
384
385    /**
386     * Creates a PlaybackGlueHost to host PlaybackGlue. App may override this if it overrides
387     * {@link #onCreateVideoFragment()}. This method must be called after calling Fragment
388     * super.onCreate(). When override this method, app may call
389     * {@link #findOrCreateVideoFragment()} to get or create a fragment.
390     *
391     * @return A new PlaybackGlueHost to host PlaybackGlue.
392     * @see #onCreateVideoFragment()
393     * @see #findOrCreateVideoFragment()
394     * @see #setupVideoPlayback(PlaybackGlue)
395     */
396    public PlaybackGlueHost onCreateGlueHost() {
397        return new VideoFragmentGlueHost((VideoFragment) findOrCreateVideoFragment());
398    }
399
400    PlaybackGlueHost createGlueHost() {
401        PlaybackGlueHost host = onCreateGlueHost();
402        if (mInitialControlVisible) {
403            host.showControlsOverlay(false);
404        } else {
405            host.hideControlsOverlay(false);
406        }
407        return host;
408    }
409
410    /**
411     * Adds or gets fragment for rendering video in DetailsFragment. A subclass that
412     * overrides {@link #onCreateGlueHost()} should call this method to get a fragment for creating
413     * a {@link PlaybackGlueHost}.
414     *
415     * @return Fragment the added or restored fragment responsible for rendering video.
416     * @see #onCreateGlueHost()
417     */
418    public final Fragment findOrCreateVideoFragment() {
419        return mFragment.findOrCreateVideoFragment();
420    }
421
422    /**
423     * Convenient method to set Bitmap in cover drawable. If app is not using default
424     * {@link FitWidthBitmapDrawable}, app should not use this method  It's safe to call
425     * setCoverBitmap() before calling {@link #enableParallax()}.
426     *
427     * @param bitmap bitmap to set as cover.
428     */
429    public final void setCoverBitmap(Bitmap bitmap) {
430        mCoverBitmap = bitmap;
431        Drawable drawable = getCoverDrawable();
432        if (drawable instanceof FitWidthBitmapDrawable) {
433            ((FitWidthBitmapDrawable) drawable).setBitmap(mCoverBitmap);
434        }
435    }
436
437    /**
438     * Returns Bitmap set by {@link #setCoverBitmap(Bitmap)}.
439     *
440     * @return Bitmap for cover drawable.
441     */
442    public final Bitmap getCoverBitmap() {
443        return mCoverBitmap;
444    }
445
446    /**
447     * Returns color set by {@link #setSolidColor(int)}.
448     *
449     * @return Solid color used for bottom drawable.
450     */
451    public final @ColorInt int getSolidColor() {
452        return mSolidColor;
453    }
454
455    /**
456     * Convenient method to set color in bottom drawable. If app is not using default
457     * {@link ColorDrawable}, app should not use this method. It's safe to call setSolidColor()
458     * before calling {@link #enableParallax()}.
459     *
460     * @param color color for bottom drawable.
461     */
462    public final void setSolidColor(@ColorInt int color) {
463        mSolidColor = color;
464        Drawable bottomDrawable = getBottomDrawable();
465        if (bottomDrawable instanceof ColorDrawable) {
466            ((ColorDrawable) bottomDrawable).setColor(color);
467        }
468    }
469
470    /**
471     * Sets default parallax offset in pixels for bitmap moving vertically. This method must
472     * be called before {@link #enableParallax()}.
473     *
474     * @param offset Offset in pixels (e.g. 120).
475     * @see #enableParallax()
476     */
477    public final void setParallaxDrawableMaxOffset(int offset) {
478        if (mParallaxDrawable != null) {
479            throw new IllegalStateException("enableParallax already called");
480        }
481        mParallaxDrawableMaxOffset = offset;
482    }
483
484    /**
485     * Returns Default parallax offset in pixels for bitmap moving vertically.
486     * When 0, a default value would be used.
487     *
488     * @return Default parallax offset in pixels for bitmap moving vertically.
489     * @see #enableParallax()
490     */
491    public final int getParallaxDrawableMaxOffset() {
492        return mParallaxDrawableMaxOffset;
493    }
494
495}
496