1 // CHECKSTYLE:OFF Generated code
2 /* This file is auto-generated from VideoDetailsFragmentBackgroundController.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  */
19 package android.support.v17.leanback.app;
20 
21 import android.animation.PropertyValuesHolder;
22 import android.support.v4.app.Fragment;
23 import android.graphics.Bitmap;
24 import android.graphics.Color;
25 import android.graphics.drawable.ColorDrawable;
26 import android.graphics.drawable.Drawable;
27 import android.support.annotation.ColorInt;
28 import android.support.annotation.NonNull;
29 import android.support.annotation.Nullable;
30 import android.support.v17.leanback.R;
31 import android.support.v17.leanback.graphics.FitWidthBitmapDrawable;
32 import android.support.v17.leanback.media.PlaybackGlue;
33 import android.support.v17.leanback.media.PlaybackGlueHost;
34 import android.support.v17.leanback.widget.DetailsParallaxDrawable;
35 import android.support.v17.leanback.widget.ParallaxTarget;
36 
37 /**
38  * Controller for DetailsSupportFragment 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  * DetailsSupportFragmentBackgroundController mController = new DetailsSupportFragmentBackgroundController(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 #onCreateVideoSupportFragment()} and
107  * {@link #onCreateGlueHost()}.
108  * </p>
109  *
110  */
111 public class DetailsSupportFragmentBackgroundController {
112 
113     final DetailsSupportFragment 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 mLastVideoSupportFragmentForGlueHost;
124 
125     /**
126      * Creates a DetailsSupportFragmentBackgroundController for a DetailsSupportFragment. Note that
127      * each DetailsSupportFragment can only associate with one DetailsSupportFragmentBackgroundController.
128      *
129      * @param fragment The DetailsSupportFragment to control background and embedded video playing.
130      * @throws IllegalStateException If fragment was already associated with another controller.
131      */
DetailsSupportFragmentBackgroundController(DetailsSupportFragment fragment)132     public DetailsSupportFragmentBackgroundController(DetailsSupportFragment fragment) {
133         if (fragment.mDetailsBackgroundController != null) {
134             throw new IllegalStateException("Each DetailsSupportFragment is allowed to initialize "
135                     + "DetailsSupportFragmentBackgroundController 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      */
enableParallax()152     public void enableParallax() {
153         int offset = mParallaxDrawableMaxOffset;
154         if (offset == 0) {
155             offset = mFragment.getContext().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      */
enableParallax(@onNull Drawable coverDrawable, @NonNull Drawable bottomDrawable, @Nullable ParallaxTarget.PropertyValuesHolderTarget coverDrawableParallaxTarget)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                 mFragment.getContext(),
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 VideoSupportFragment and VideoSupportFragmentGlueHost 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 #onCreateVideoSupportFragment()
230      * @see #onCreateGlueHost().
231      */
232     @SuppressWarnings("ReferenceEquality")
setupVideoPlayback(@onNull PlaybackGlue playbackGlue)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                     || mLastVideoSupportFragmentForGlueHost != findOrCreateVideoSupportFragment()) {
249                 mPlaybackGlue.setHost(createGlueHost());
250                 mLastVideoSupportFragmentForGlueHost = findOrCreateVideoSupportFragment();
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      */
getPlaybackGlue()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      */
canNavigateToVideoSupportFragment()274     public boolean canNavigateToVideoSupportFragment() {
275         return mPlaybackGlue != null;
276     }
277 
switchToVideoBeforeCreate()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 #canNavigateToVideoSupportFragment()}. If the method is called in DetailsSupportFragment.onCreate()
286      * it will make video fragment to be initially focused once it is created.
287      * <p>
288      * Calling switchToVideo() in DetailsSupportFragment.onCreate() will clear the activity enter
289      * transition and shared element transition.
290      * </p>
291      * <p>
292      * If switchToVideo() is called after {@link DetailsSupportFragment#prepareEntranceTransition()} and
293      * before {@link DetailsSupportFragment#onEntranceTransitionEnd()}, it will be ignored.
294      * </p>
295      * <p>
296      * If {@link DetailsSupportFragment#prepareEntranceTransition()} is called after switchToVideo(), an
297      * IllegalStateException will be thrown.
298      * </p>
299      */
switchToVideo()300     public final void switchToVideo() {
301         mFragment.switchToVideo();
302     }
303 
304     /**
305      * Switch to rows fragment.
306      */
switchToRows()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      */
onStart()315     void onStart() {
316         if (!mCanUseHost) {
317             mCanUseHost = true;
318             if (mPlaybackGlue != null) {
319                 mPlaybackGlue.setHost(createGlueHost());
320                 mLastVideoSupportFragmentForGlueHost = findOrCreateVideoSupportFragment();
321             }
322         }
323         if (mPlaybackGlue != null && mPlaybackGlue.isPrepared()) {
324             mPlaybackGlue.play();
325         }
326     }
327 
onStop()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      */
disableVideoParallax()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      */
getCoverDrawable()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      */
getBottomDrawable()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 VideoSupportFragment} 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      */
onCreateVideoSupportFragment()381     public Fragment onCreateVideoSupportFragment() {
382         return new VideoSupportFragment();
383     }
384 
385     /**
386      * Creates a PlaybackGlueHost to host PlaybackGlue. App may override this if it overrides
387      * {@link #onCreateVideoSupportFragment()}. This method must be called after calling Fragment
388      * super.onCreate(). When override this method, app may call
389      * {@link #findOrCreateVideoSupportFragment()} to get or create a fragment.
390      *
391      * @return A new PlaybackGlueHost to host PlaybackGlue.
392      * @see #onCreateVideoSupportFragment()
393      * @see #findOrCreateVideoSupportFragment()
394      * @see #setupVideoPlayback(PlaybackGlue)
395      */
onCreateGlueHost()396     public PlaybackGlueHost onCreateGlueHost() {
397         return new VideoSupportFragmentGlueHost((VideoSupportFragment) findOrCreateVideoSupportFragment());
398     }
399 
createGlueHost()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 DetailsSupportFragment. 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      */
findOrCreateVideoSupportFragment()418     public final Fragment findOrCreateVideoSupportFragment() {
419         return mFragment.findOrCreateVideoSupportFragment();
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      */
setCoverBitmap(Bitmap bitmap)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      */
getCoverBitmap()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      */
getSolidColor()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      */
setSolidColor(@olorInt int color)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      */
setParallaxDrawableMaxOffset(int offset)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      */
getParallaxDrawableMaxOffset()491     public final int getParallaxDrawableMaxOffset() {
492         return mParallaxDrawableMaxOffset;
493     }
494 
495 }
496