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  */
14 package com.example.android.leanback;
15 
16 import android.content.Context;
17 import android.content.Intent;
18 import android.graphics.Bitmap;
19 import android.graphics.drawable.Drawable;
20 import android.net.Uri;
21 import android.os.Bundle;
22 import android.os.Handler;
23 import android.util.Log;
24 import android.view.LayoutInflater;
25 import android.view.View;
26 import android.view.ViewGroup;
27 
28 import androidx.core.app.ActivityOptionsCompat;
29 import androidx.core.content.res.ResourcesCompat;
30 import androidx.leanback.app.DetailsFragmentBackgroundController;
31 import androidx.leanback.media.MediaPlayerAdapter;
32 import androidx.leanback.media.MediaPlayerGlue;
33 import androidx.leanback.media.PlaybackGlue;
34 import androidx.leanback.media.PlaybackTransportControlGlue;
35 import androidx.leanback.widget.Action;
36 import androidx.leanback.widget.ArrayObjectAdapter;
37 import androidx.leanback.widget.ClassPresenterSelector;
38 import androidx.leanback.widget.DetailsOverviewRow;
39 import androidx.leanback.widget.FullWidthDetailsOverviewRowPresenter;
40 import androidx.leanback.widget.FullWidthDetailsOverviewSharedElementHelper;
41 import androidx.leanback.widget.HeaderItem;
42 import androidx.leanback.widget.ImageCardView;
43 import androidx.leanback.widget.ListRow;
44 import androidx.leanback.widget.ListRowPresenter;
45 import androidx.leanback.widget.OnActionClickedListener;
46 import androidx.leanback.widget.OnItemViewClickedListener;
47 import androidx.leanback.widget.OnItemViewSelectedListener;
48 import androidx.leanback.widget.Presenter;
49 import androidx.leanback.widget.Row;
50 import androidx.leanback.widget.RowPresenter;
51 import androidx.leanback.widget.SparseArrayObjectAdapter;
52 
53 import org.jspecify.annotations.NonNull;
54 
55 public class NewDetailsFragment extends androidx.leanback.app.DetailsFragment {
56 
57     private static final String TAG = "leanback.DetailsFragment";
58 
59     private static final int NUM_ROWS = 3;
60     private ArrayObjectAdapter mRowsAdapter;
61     private PhotoItem mPhotoItem;
62     final CardPresenter cardPresenter = new CardPresenter();
63 
64     private static final int ACTION_PLAY = 1;
65     private static final int ACTION_RENT = 2;
66     private static final int ACTION_BUY = 3;
67 
68     private boolean TEST_SHARED_ELEMENT_TRANSITION = true;
69     private boolean TEST_BACKGROUND_PLAYER;
70 
71     private static final long TIME_TO_LOAD_OVERVIEW_ROW_MS = 1000;
72     private static final long TIME_TO_LOAD_RELATED_ROWS_MS = 2000;
73 
74     private Action mActionPlay;
75     private Action mActionRent;
76     private Action mActionBuy;
77 
78     private FullWidthDetailsOverviewSharedElementHelper mHelper;
79     private BackgroundHelper mBackgroundHelper; // used to download bitmap async.
80     private final DetailsFragmentBackgroundController mDetailsBackground =
81             new DetailsFragmentBackgroundController(this);
82 
setupTrailerVideo()83     void setupTrailerVideo() {
84         MediaPlayerGlue mediaPlayerGlue = new MediaPlayerGlue(getActivity());
85         mDetailsBackground.setupVideoPlayback(mediaPlayerGlue);
86         mediaPlayerGlue.setMode(MediaPlayerGlue.REPEAT_ONE);
87         mediaPlayerGlue.setArtist("A Googler");
88         mediaPlayerGlue.setTitle("Diving with Sharks Trailer");
89         mediaPlayerGlue.setMediaSource(Uri.parse("android.resource://com.example.android.leanback/"
90                 + "raw/browse"));
91     }
92 
setupMainVideo()93     void setupMainVideo() {
94         Context context = getActivity();
95         MediaPlayerAdapter adapter = new MediaPlayerAdapter(context);
96         PlaybackTransportControlGlue<MediaPlayerAdapter> mediaPlayerGlue =
97                 new PlaybackTransportControlGlue(context, adapter);
98         mDetailsBackground.setupVideoPlayback(mediaPlayerGlue);
99         mediaPlayerGlue.setSubtitle("A Googler");
100         mediaPlayerGlue.setTitle("Diving with Sharks");
101         mediaPlayerGlue.getPlayerAdapter().setDataSource(Uri.parse(
102                 "https://storage.googleapis.com/android-tv/Sample videos/April Fool's "
103                         + "2013/Explore Treasure Mode with Google Maps.mp4"));
104         mediaPlayerGlue.addPlayerCallback(new PlaybackGlue.PlayerCallback() {
105             @Override
106             public void onPreparedStateChanged(@NonNull PlaybackGlue glue) {
107                 super.onPreparedStateChanged(glue);
108                 PlaybackTransportControlGlue controlGlue = (PlaybackTransportControlGlue) glue;
109                 controlGlue.setSeekProvider(new PlaybackSeekDiskDataProvider(
110                         controlGlue.getDuration(), 1000,
111                         "/sdcard/seek/frame_%04d.jpg"));
112             }
113         });
114     }
115 
116     @Override
onCreate(Bundle savedInstanceState)117     public void onCreate(Bundle savedInstanceState) {
118         Log.i(TAG, "onCreate");
119         super.onCreate(savedInstanceState);
120 
121         mBackgroundHelper = new BackgroundHelper(getActivity());
122         mDetailsBackground.enableParallax();
123         if (TEST_BACKGROUND_PLAYER) {
124             if (MovieData.sStatus == MovieData.STATUS_INIT) {
125                 // not own/rented, play trailer
126                 setupTrailerVideo();
127             } else {
128                 // bought or rented, play the main content
129                 setupMainVideo();
130                 // hide details main ui
131                 mDetailsBackground.switchToVideo();
132             }
133         }
134 
135         final Context context = getActivity();
136         setBadgeDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_title,
137                 context.getTheme()));
138         setTitle("Leanback Sample App");
139         if (!TEST_BACKGROUND_PLAYER) {
140             setOnSearchClickedListener(new View.OnClickListener() {
141                 @Override
142                 public void onClick(View view) {
143                     Intent intent = new Intent(getActivity(), SearchActivity.class);
144                     startActivity(intent);
145                 }
146             });
147         }
148 
149         mActionPlay = new Action(ACTION_PLAY, "Play");
150         mActionRent = new Action(ACTION_RENT, "Rent", "$3.99", ResourcesCompat.getDrawable(
151                 context.getResources(), R.drawable.ic_action_a, context.getTheme()));
152         mActionBuy = new Action(ACTION_BUY, "Buy $9.99");
153 
154         ClassPresenterSelector ps = new ClassPresenterSelector();
155         FullWidthDetailsOverviewRowPresenter dorPresenter =
156                 new FullWidthDetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
157         dorPresenter.setOnActionClickedListener(new OnActionClickedListener() {
158             @Override
159             public void onActionClicked(Action action) {
160                 final Context context = getActivity();
161                 DetailsOverviewRow dor = (DetailsOverviewRow) mRowsAdapter.get(0);
162                 if (action.getId() == ACTION_BUY) {
163                     // on the UI thread, we can modify actions adapter directly
164                     SparseArrayObjectAdapter actions = (SparseArrayObjectAdapter)
165                             dor.getActionsAdapter();
166                     actions.set(ACTION_PLAY, mActionPlay);
167                     actions.clear(ACTION_RENT);
168                     actions.clear(ACTION_BUY);
169                     boolean previousRented = MovieData.sStatus == MovieData.STATUS_RENTED;
170                     MovieData.sStatus = MovieData.STATUS_OWN;
171                     dor.setItem(getDisplayTitle(mPhotoItem.getTitle()));
172                     dor.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(),
173                             R.drawable.details_img_16x9, context.getTheme()));
174                     if (TEST_BACKGROUND_PLAYER) {
175                         if (!previousRented) {
176                             setupMainVideo();
177                             mDetailsBackground.switchToVideo();
178                         }
179                     } else {
180                         Intent intent = new Intent(context, PlaybackActivity.class);
181                         getActivity().startActivity(intent);
182                     }
183                 } else if (action.getId() == ACTION_RENT) {
184                     // on the UI thread, we can modify actions adapter directly
185                     SparseArrayObjectAdapter actions = (SparseArrayObjectAdapter)
186                             dor.getActionsAdapter();
187                     actions.set(ACTION_PLAY, mActionPlay);
188                     actions.clear(ACTION_RENT);
189                     MovieData.sStatus = MovieData.STATUS_RENTED;
190                     dor.setItem(getDisplayTitle(mPhotoItem.getTitle()));
191                     if (TEST_BACKGROUND_PLAYER) {
192                         setupMainVideo();
193                         mDetailsBackground.switchToVideo();
194                     } else {
195                         Intent intent = new Intent(context, PlaybackActivity.class);
196                         getActivity().startActivity(intent);
197                     }
198                 } else if (action.getId() == ACTION_PLAY) {
199                     if (TEST_BACKGROUND_PLAYER) {
200                         mDetailsBackground.switchToVideo();
201                     } else {
202                         Intent intent = new Intent(context, PlaybackActivity.class);
203                         getActivity().startActivity(intent);
204                     }
205                 }
206             }
207         });
208 
209         ps.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
210         ps.addClassPresenter(ListRow.class, new ListRowPresenter());
211 
212         mRowsAdapter = new ArrayObjectAdapter(ps);
213         updateAdapter();
214 
215         setOnItemViewClickedListener(new OnItemViewClickedListener() {
216             @Override
217             public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
218                     RowPresenter.ViewHolder rowViewHolder, Row row) {
219                 Log.i(TAG, "onItemClicked: " + item + " row " + row);
220                 if (item instanceof PhotoItem) {
221                     Intent intent = new Intent(getActivity(), DetailsActivity.class);
222                     intent.putExtra(DetailsActivity.EXTRA_ITEM, (PhotoItem) item);
223 
224                     Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(
225                             getActivity(),
226                             ((ImageCardView) itemViewHolder.view).getMainImageView(),
227                             DetailsActivity.SHARED_ELEMENT_NAME).toBundle();
228                     getActivity().startActivity(intent, bundle);
229                 }
230             }
231         });
232         setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
233             @Override
234             public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
235                     RowPresenter.ViewHolder rowViewHolder, Row row) {
236                 Log.i(TAG, "onItemSelected: " + item + " row " + row);
237             }
238         });
239 
240         if (TEST_SHARED_ELEMENT_TRANSITION) {
241             mHelper = new FullWidthDetailsOverviewSharedElementHelper();
242             mHelper.setSharedElementEnterTransition(getActivity(),
243                     DetailsActivity.SHARED_ELEMENT_NAME);
244             dorPresenter.setListener(mHelper);
245             dorPresenter.setParticipatingEntranceTransition(false);
246         } else {
247             dorPresenter.setParticipatingEntranceTransition(true);
248         }
249     }
250 
251     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)252     public View onCreateView(LayoutInflater inflater, ViewGroup container,
253             Bundle savedInstanceState) {
254         View view = super.onCreateView(inflater, container, savedInstanceState);
255         return view;
256     }
257 
setBackgroundVideo(boolean backgroundVideo)258     public void setBackgroundVideo(boolean backgroundVideo) {
259         TEST_BACKGROUND_PLAYER = backgroundVideo;
260     }
261 
setItem(PhotoItem photoItem)262     public void setItem(PhotoItem photoItem) {
263         mPhotoItem = photoItem;
264         updateAdapter();
265     }
266 
getDisplayTitle(String title)267     static String getDisplayTitle(String title) {
268         switch (MovieData.sStatus) {
269             case MovieData.STATUS_OWN:
270                 title = title + "(Owned)";
271                 break;
272             case MovieData.STATUS_RENTED:
273                 title = title + "(Rented)";
274                 break;
275             case MovieData.STATUS_INIT:
276             default:
277         }
278         return title;
279     }
280 
updateAdapter()281     void updateAdapter() {
282         if (mRowsAdapter == null) {
283             return;
284         }
285         mRowsAdapter.clear();
286         new Handler().postDelayed(new Runnable() {
287             @Override
288             public void run() {
289                 final Context context = getActivity();
290                 if (context == null) {
291                     return;
292                 }
293 
294                 DetailsOverviewRow dor = new DetailsOverviewRow(
295                         getDisplayTitle(mPhotoItem.getTitle()));
296                 dor.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(),
297                         mPhotoItem.getImageResourceId(), context.getTheme()));
298                 SparseArrayObjectAdapter adapter = new SparseArrayObjectAdapter();
299                 switch (MovieData.sStatus) {
300                     case MovieData.STATUS_INIT:
301                         adapter.set(ACTION_RENT, mActionRent);
302                         adapter.set(ACTION_BUY, mActionBuy);
303                         break;
304                     case MovieData.STATUS_OWN:
305                         adapter.set(ACTION_PLAY, mActionPlay);
306                         break;
307                     case MovieData.STATUS_RENTED:
308                         adapter.set(ACTION_PLAY, mActionPlay);
309                         adapter.set(ACTION_BUY, mActionBuy);
310                         break;
311                 }
312                 // one line text with icon
313                 Drawable d = ResourcesCompat.getDrawable(context.getResources(),
314                         R.drawable.ic_action_a, context.getTheme());
315                 adapter.set(202, new Action(202, "Top", null, d));
316                 dor.setActionsAdapter(adapter);
317                 mRowsAdapter.add(0, dor);
318                 setSelectedPosition(0, true);
319                 if (TEST_SHARED_ELEMENT_TRANSITION) {
320                     if (mHelper != null && !mHelper.getAutoStartSharedElementTransition()) {
321                         mHelper.startPostponedEnterTransition();
322                     }
323                 }
324             }
325         }, TIME_TO_LOAD_OVERVIEW_ROW_MS);
326 
327         new Handler().postDelayed(new Runnable() {
328             @Override
329             public void run() {
330                 if (getActivity() == null) {
331                     return;
332                 }
333                 for (int i = 0; i < NUM_ROWS; ++i) {
334                     ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
335                     listRowAdapter.add(new PhotoItem("Hello world", R.drawable.gallery_photo_1));
336                     listRowAdapter.add(new PhotoItem("This is a test", R.drawable.gallery_photo_2));
337                     listRowAdapter.add(new PhotoItem("Android TV", R.drawable.gallery_photo_3));
338                     listRowAdapter.add(new PhotoItem("Leanback", R.drawable.gallery_photo_4));
339                     HeaderItem header = new HeaderItem(i, "Row " + i);
340                     mRowsAdapter.add(new ListRow(header, listRowAdapter));
341                 }
342             }
343         }, TIME_TO_LOAD_RELATED_ROWS_MS);
344         setAdapter(mRowsAdapter);
345     }
346 
347     @Override
onStart()348     public void onStart() {
349         super.onStart();
350 
351         // Restore background drawable in onStart():
352         mBackgroundHelper.loadBitmap(R.drawable.spiderman,
353                 new BackgroundHelper.BitmapLoadCallback() {
354                     @Override
355                     public void onBitmapLoaded(Bitmap bitmap) {
356                         mDetailsBackground.setCoverBitmap(bitmap);
357                     }
358                 });
359     }
360 
361     @Override
onStop()362     public void onStop() {
363         mDetailsBackground.setCoverBitmap(null);
364         super.onStop();
365     }
366 }
367