Java tutorial
/** * This file was modified by Amazon: * Copyright 2015-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0/ * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ /* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.amazon.android.tv.tenfoot.ui.fragments; import com.amazon.android.contentbrowser.ContentBrowser; import com.amazon.android.model.Action; import com.amazon.android.model.content.Content; import com.amazon.android.model.content.ContentContainer; import com.amazon.android.utils.Helpers; import com.amazon.android.tv.tenfoot.R; import com.amazon.android.tv.tenfoot.presenter.CardPresenter; import com.amazon.android.tv.tenfoot.presenter.DetailsDescriptionPresenter; import com.amazon.android.tv.tenfoot.ui.activities.ContentDetailsActivity; import com.amazon.android.tv.tenfoot.utils.LeanbackHelpers; import com.bumptech.glide.Glide; import com.bumptech.glide.request.animation.GlideAnimation; import com.bumptech.glide.request.target.SimpleTarget; import android.app.NotificationManager; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v17.leanback.app.BackgroundManager; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.ClassPresenterSelector; import android.support.v17.leanback.widget.DetailsOverviewRow; import android.support.v17.leanback.widget.DetailsOverviewRowPresenter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.HorizontalGridView; import android.support.v17.leanback.widget.ImageCardView; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; import android.support.v17.leanback.widget.RowHeaderPresenter; import android.support.v17.leanback.widget.TenFootActionPresenterSelector; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.support.v17.leanback.widget.SparseArrayObjectAdapter; import android.support.v4.app.ActivityOptionsCompat; import android.support.v4.content.ContextCompat; import android.util.DisplayMetrics; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import java.util.List; /** * LeanbackDetailsFragment extends DetailsFragment, a Wrapper fragment for leanback * content_details_activity_layout screens. * It shows a detailed view of video and its meta plus related videos. */ public class ContentDetailsFragment extends android.support.v17.leanback.app.DetailsFragment { private static final String TAG = ContentDetailsFragment.class.getSimpleName(); private static final int DETAIL_THUMB_WIDTH = 264; private static final int DETAIL_THUMB_HEIGHT = 198; private static final int NO_NOTIFICATION = -1; private Content mSelectedContent; private ArrayObjectAdapter mAdapter; private ClassPresenterSelector mPresenterSelector; private BackgroundManager mBackgroundManager; private Drawable mDefaultBackground; private DisplayMetrics mMetrics; private boolean mShowRecommendations; SparseArrayObjectAdapter mActionAdapter = new SparseArrayObjectAdapter(); // Decides whether the action button should be enabled or not. private boolean actionInProgress = false; @Override public void onCreate(Bundle savedInstanceState) { Log.d(TAG, "onCreate DetailsFragment"); super.onCreate(savedInstanceState); prepareBackgroundManager(); mSelectedContent = ContentBrowser.getInstance(getActivity()).getLastSelectedContent(); mShowRecommendations = ContentBrowser.getInstance(getActivity()).isShowRecommendations(); } @Override public void onStart() { Log.v(TAG, "onStart called."); super.onStart(); if (mSelectedContent != null || checkGlobalSearchIntent()) { removeNotification( getActivity().getIntent().getIntExtra(ContentDetailsActivity.NOTIFICATION_ID, NO_NOTIFICATION)); setupAdapter(); setupDetailsOverviewRow(); setupDetailsOverviewRowPresenter(); if (mShowRecommendations) { setupRelatedContentRow(); } setupContentListRowPresenter(); updateBackground(mSelectedContent.getBackgroundImageUrl()); setOnItemViewClickedListener(new ItemViewClickedListener()); } else { Log.v(TAG, "Start CONTENT_HOME_SCREEN."); ContentBrowser.getInstance(getActivity()).switchToScreen(ContentBrowser.CONTENT_HOME_SCREEN); } } /** * Overriding this method to return null since we do not want the title view to be available * in ContentDetails page. * {@inheritDoc} */ protected View inflateTitle(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) { return null; } private void removeNotification(int notificationId) { Log.v(TAG, "removeNotification:" + notificationId); if (notificationId != NO_NOTIFICATION) { NotificationManager notificationManager = (NotificationManager) getActivity() .getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.cancel(notificationId); } } /** * Check if there is a global search intent. */ private boolean checkGlobalSearchIntent() { Log.v(TAG, "checkGlobalSearchIntent called."); Intent intent = getActivity().getIntent(); String intentAction = intent.getAction(); String globalSearch = getString(R.string.global_search); if (globalSearch.equalsIgnoreCase(intentAction)) { Uri intentData = intent.getData(); Log.d(TAG, "action: " + intentAction + " intentData:" + intentData); int selectedIndex = Integer.parseInt(intentData.getLastPathSegment()); ContentContainer contentContainer = ContentBrowser.getInstance(getActivity()).getRootContentContainer(); int contentTally = 0; if (contentContainer == null) { return false; } for (Content content : contentContainer) { ++contentTally; if (selectedIndex == contentTally) { mSelectedContent = content; return true; } } } return false; } private void prepareBackgroundManager() { mBackgroundManager = BackgroundManager.getInstance(getActivity()); mBackgroundManager.attach(getActivity().getWindow()); mDefaultBackground = ContextCompat.getDrawable(getActivity(), android.R.color.transparent); mMetrics = new DisplayMetrics(); getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics); } private void updateBackground(String uri) { Log.v(TAG, "updateBackground called"); if (Helpers.DEBUG) { Log.v(TAG, "updateBackground called: " + uri); } Glide.with(getActivity()).load(uri).asBitmap().listener(new Helpers.LoggingListener<>()).centerCrop() .error(mDefaultBackground) .into(new SimpleTarget<Bitmap>(mMetrics.widthPixels, mMetrics.heightPixels) { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { Bitmap bitmap = Helpers.adjustOpacity(resource, getResources().getInteger(R.integer.content_details_fragment_bg_opacity)); mBackgroundManager.setBitmap(bitmap); } }); } private void setupAdapter() { Log.v(TAG, "setupAdapter called."); mPresenterSelector = new ClassPresenterSelector(); mAdapter = new ArrayObjectAdapter(mPresenterSelector); setAdapter(mAdapter); } public void updateActions() { List<Action> contentActionList = ContentBrowser.getInstance(getActivity()) .getContentActionList(mSelectedContent); int i = 0; mActionAdapter.clear(); for (Action action : contentActionList) { mActionAdapter.set(i++, LeanbackHelpers.translateActionToLeanBackAction(action)); } actionInProgress = false; } private void setupDetailsOverviewRow() { Log.d(TAG, "doInBackground"); if (Helpers.DEBUG) { Log.d(TAG, "Selected content is: " + mSelectedContent.toString()); } final DetailsOverviewRow row = new DetailsOverviewRow(mSelectedContent); row.setActionsAdapter(new ArrayObjectAdapter(new TenFootActionPresenterSelector())); row.setImageDrawable(ContextCompat.getDrawable(getActivity(), android.R.color.transparent)); int width = Helpers.convertDpToPixel(getActivity().getApplicationContext(), DETAIL_THUMB_WIDTH); int height = Helpers.convertDpToPixel(getActivity().getApplicationContext(), DETAIL_THUMB_HEIGHT); Glide.with(getActivity()).load(mSelectedContent.getCardImageUrl()).asBitmap() .listener(new Helpers.LoggingListener<>()).centerCrop().error(android.R.color.transparent) .into(new SimpleTarget<Bitmap>(width, height) { @Override public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) { Log.d(TAG, "content_details_activity_layout overview card image url " + "ready: " + resource); row.setImageBitmap(getActivity(), Helpers.roundCornerImage(getActivity(), resource, getResources().getInteger(R.integer.details_overview_image_corner_radius))); mAdapter.notifyArrayItemRangeChanged(0, mAdapter.size()); } }); updateActions(); row.setActionsAdapter(mActionAdapter); mAdapter.add(row); } private void setupDetailsOverviewRowPresenter() { DetailsDescriptionPresenter detailsDescPresenter = new DetailsDescriptionPresenter(); // Set detail background and style. DetailsOverviewRowPresenter detailsPresenter = new DetailsOverviewRowPresenter(detailsDescPresenter) { @Override protected void initializeRowViewHolder(RowPresenter.ViewHolder vh) { super.initializeRowViewHolder(vh); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { vh.view.findViewById(R.id.details_overview_image) .setTransitionName(ContentDetailsActivity.SHARED_ELEMENT_NAME); } } }; detailsPresenter.setBackgroundColor(getResources().getColor(android.R.color.transparent)); detailsPresenter.setStyleLarge(true); // Hook up transition element. detailsPresenter.setSharedElementEnterTransition(getActivity(), ContentDetailsActivity.SHARED_ELEMENT_NAME); detailsPresenter.setOnActionClickedListener(action -> { try { if (actionInProgress) { return; } actionInProgress = true; int actionId = (int) action.getId(); Log.v(TAG, "detailsPresenter.setOnActionClicked:" + actionId); ContentBrowser.getInstance(getActivity()).actionTriggered(getActivity(), mSelectedContent, actionId); } catch (Exception e) { Log.e(TAG, "caught exception while clicking action", e); actionInProgress = false; } }); mPresenterSelector.addClassPresenter(DetailsOverviewRow.class, detailsPresenter); } /** * Builds the related content row. Uses contents from the selected content's category. */ private void setupRelatedContentRow() { ContentContainer recommended = ContentBrowser.getInstance(getActivity()) .getRecommendedListOfAContentAsAContainer(mSelectedContent); ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(new CardPresenter()); for (Content c : recommended) { listRowAdapter.add(c); } // Only add the header and row for recommendations if there are any recommended content. if (listRowAdapter.size() > 0) { HeaderItem header = new HeaderItem(0, recommended.getName()); mAdapter.add(new ListRow(header, listRowAdapter)); } } private void setupContentListRowPresenter() { ListRowPresenter presenter = new ListRowPresenter(); presenter.setHeaderPresenter(new RowHeaderPresenter()); mPresenterSelector.addClassPresenter(ListRow.class, presenter); } private final class ItemViewClickedListener implements OnItemViewClickedListener { @Override public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item, RowPresenter.ViewHolder rowViewHolder, Row row) { if (item instanceof Content) { Content content = (Content) item; if (Helpers.DEBUG) { Log.d(TAG, "Item: " + content.getId()); } Bundle bundle = ActivityOptionsCompat.makeSceneTransitionAnimation(getActivity(), ((ImageCardView) itemViewHolder.view).getMainImageView(), ContentDetailsActivity.SHARED_ELEMENT_NAME).toBundle(); ContentBrowser.getInstance(getActivity()).setLastSelectedContent(content) .switchToScreen(ContentBrowser.CONTENT_DETAILS_SCREEN, bundle); } } } @Override public void onResume() { Log.v(TAG, "onResume called."); super.onResume(); updateActionsProperties(); actionInProgress = false; } /** * Since we do not have direct access to the details overview actions row, we are adding a * delayed handler that waits for some time, searches for the row and then updates the * properties. This is not a fool-proof method, * > In slow devices its possible that this does not succeed in achieving the desired result. * > In fast devices its possible that the update is clearly visible to the user. * TODO: Find a better approach to update action properties */ private void updateActionsProperties() { Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(() -> { View view = getView(); if (view != null) { HorizontalGridView horizontalGridView = (HorizontalGridView) view .findViewById(R.id.details_overview_actions); if (horizontalGridView != null) { // This is required to make sure this button gets the focus whenever // detailsFragment is resumed. horizontalGridView.requestFocus(); for (int i = 0; i < horizontalGridView.getChildCount(); i++) { final Button button = (Button) horizontalGridView.getChildAt(i); if (button != null) { // Button objects are recreated every time MovieDetailsFragment is // created or restored, so we have to bind OnKeyListener to them on // resuming the Fragment. button.setOnKeyListener((v, keyCode, keyEvent) -> { if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE && keyEvent.getAction() == KeyEvent.ACTION_DOWN) { button.performClick(); } return false; }); } } } } }, 400); } }