Recently (actually not recently, I noticed this change maybe 1-2 month ago), leanback launcher app, Android TV’s default home launcher app, has updated. I noticed 2 changes for its behavior.
I also want to discuss about current situation for home launcher apps for Android TV.
Icon layout customization
Current leanback launcher has really few features we can customize its configuration. But updated app allows you to customize icon layout by long-pressing center button on the app launcher icon.
It seems it is only allowed for “Apps” and “Games” rows which are default Android TV rows, and other rows (for example, “Inputs” row of SONY BRAVIA TV) layout cannot be customized.
Other home launcher app is not allowed to launch as “home” app
Have you ever think of changing home launcher app of your Android TV? There are still few, but some home launcher apps are published on Android TV platform as well.
HALauncher – Android TV: It offers you more customization features than default leanback launcher for now. For example you can set background picture or you can customize app icon layout & row layout etc.
However, after recent update for Android TV, we cannot make these home launcher app as “home” app. More technically, third party app cannot get the intent of category.HOME.
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<!--
Below 2 lines are for HOME launcher app. They get the intent of "HOME" button key press.
(ref: http://ssmomonga.blogspot.sg/2011/12/blog-post.html)
-->
<category android:name="android.intent.category.HOME" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
<!--<category android:name="android.intent.category.LAUNCHER" />-->
</intent-filter>
This issue is also mentioned at HALauncher’s app description as
* Attention * Android TV of “All version” can not use this app as home app! When google’s apps are updated, they change system settings automatically and stop to use other than default home app. Even it’s older than Android 6.0…
There is a discussion thread “Nexus Player, Android Marshmallow, Setting default home launcher issue“, I think many users prefer Android to be free to customize its behavior by your own. Android platform should be open for any developers and users, it is not a good news that Android TV apps development is “closed” for some category area.
Home launcher apps are really big market for smartphone (For example, GO Launcher is over 100M downloaded app!), but currently Home launcher apps are implicitly restricted by Google for Android TV platform… I’m hoping that this behavior will change in near future!
If you have any opinion about this, feel free to comment on this post.
I tested YouTube Android Player API on Android TV devices. Sadly it is not working well with Android TV’s YouTube app (“YouTube for Android TV”). Only YouTube App Launcher Intents works well for now (2016.1.14).
YouTube Android Player API
YouTube Android Player API is an official library on Google Developers web site, which enables us to integrate YouTube functionality into our (third party’s) Android apps. We can control YouTube video/playlist playback from our app, and customize playback view using YouTube Android Player API.
YouTube app must be installed on user’s device
As written in the official page,
How it works
The API client library interacts with a service that is distributed as a part of the YouTube app for the Android platform.
the API is calling the API defined in YouTube app. Therefore, when the app uses YouTube Android Player API, the application users must have YouTube app installed on their devices.
If YouTube app is not installed yet…
Don’t worry. In that case Goole play store app suggest users to install/enable/update YouTube app when the API is called.
* In terms of the Android TV, almost all the devices pre-installs YouTube app. At least I know that Nexus Player and SONY BRAVIA Android TV, which are the most major Android TV devices for now, have YouTube app pre-installed. So this restriction may not be a problem.
When the YouTube app is disabled on user’s device, API shows dialog to enable YouTube App.
* In terms of the Android TV, almost all the devices pre-installs YouTube app. At least I know that Nexus Player and SONY BRAVIA Android TV, which are the most major Android TV devices for now, have YouTube app pre-installed. So this restriction may not be a problem.
Testing Sample applictions
Sample applications are available from official page, you can download API jar files together with the sample source code. Build by yourself using your own YouTube API key (it is troublesome that you need to register your own YouTube API key even if this is testing, though), and you can see the each features provided by this API.
Video Wall
Video List
Simple PlayerView
Simple PlayerFragment
Custom Player Controls
Custom Fullscreen handling
Overlay ActionBar Demo
Standalone Player
YouTube App Launcher Intents
When I tested these sample applications on Android TV, all functions do not work except for YouTube App Launcher Intents.
Concretely, if I try to navigate each features, YouTube App update dialog appears even if the YouTube App for Android TV is latest version. (Tested with “YouTube for Android TV” app version 1.3.8)
But I can test these features fine with Android phone, so I will list up each samples anyway.
Video Wall
Sample demo showing a flipping card, one card will start playing video from YouTube playlist.
Video List
Using YouTubeThumbnailViews with adapter to show list of YouTube videos.
Simple PlayerView
Demo of YouTubePlayerView to play YouTube video.
Simple PlayerFragment
Demo of YouTubePlayerFragment to play YouTube video. Appearance to the user looks same with YouTubePlayerView.
Custom Player Controls
The sample code how to control YouTubePlayer programatically from your app.
Custom Fullscreen handling
Sample code for how to handle fullscreen video playback.
Overlay ActionBar Demo
Demo for controlling Action Bar. Action bar will automatically fade away when the video is player with full screen.
Standalone Player
It demonstrates the usage of YouTubeStandalonePlayer intent.
You can start YouTube player in a separate Activity.
YouTube App Launcher Intents
This is the only available feature for Android TV app for now, and even not all but only major functionality is supported. By using YouTube Android Launcher Intents, you can launch YouTube app with specific page. But for users, they don’t usually notice application is changed. It just looks seemlessly navigated to YouTube video playback etc.
Available Intents for Android TV are following.
Play Video: create intent to start video playback by specifying YouTube Video Id
Open Playlist: create intent to open playlist page by specifying YouTube Playlist Id
Play Playlist: create intent to start playlist video playback by specifying YouTube Playlist Id
Open User: create intent to show user’s page by specifying YouTube user Id
Above picture is a example after clicking “Open User” on Android TV. It will launch user’s page of “Google”.
Implementing YouTube video playback on Android TV app?
So far, only using YouTube Intents is available. It means that you cannot embed YouTube video player view in your application, but it is only possible to launch YouTube app to do the desired job.
I’m going to write a post for YouTube video playback on Android TV app soon.
We learned BrowseFragmentto show icons, where icons are aligned horizontally when the size increases. VerticalGridFragmentis another Fragment provided by leanback support library, where it shows the icons with vertical alignment.
This post explains how to implement VerticalGridFragment. The implementation is referenced from Google’s sample implementation in the same way as before.
Create parent Activity – VerticalGridActivity
Starting by creating Activity. right click on “ui” package, [New] → [Activity] → [Blank Activity], type Activity Name as “VerticalGridActivity” and click [Finish]. It will automatically create VerticalGridActivity class, activity_vertical_grid.xml layout resource and add this Activity information to AndroidManifest.xml.
This Activity only shows VerticalGridFragment, so modify layout/activity_vertical_grid.xml (automatically generated file) as follows
No modification necessary for VerticalGridActivity class.
Create VerticalGridFragment
Now we can proceed to make our VerticalGridFragment class. right click on “ui” package, [New] → [Java Class] → type Name as “VerticalGridFragment“.
This new Fragment needs to be a subclass of leanback support library’s VerticalGridFragment class (android.support.v17.leanback.app.VerticalGridFragment). Mock implementation looks like this.
package com.corochann.androidtvapptutorial.ui;
import android.os.Bundle;
import android.util.Log;
/**
* VerticalGridFragment shows contents with vertical alignment
*/
public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
private static final String TAG = VerticalGridFragment.class.getSimpleName();
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
}
}
When I check the source code of VerticalGridFragment, it was not long. So tracking all the public set methods are not so difficult which are
setGridPresenter
setAdapter
setTitle
setOnItemViewSelectedListener
setOnItemViewClickedListener
setSelectedPosition
Each methods are explained one by one in the following. But if you want a quick minimum implementation, implementing setGridPresenter & setAdapteris enough (and setGridPresenter is MUST) for VerticalGridFragment to work.
setGridPresenter (MUST)
Specify a Presenter for VerticalGridFragment. setGridPresenter takes VerticalGridPresenter class (provided by leanback library) as argument. Following simple template code suffices in most of the case.
private static final int NUM_COLUMNS = 4;
VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
gridPresenter.setNumberOfColumns(NUM_COLUMNS);
setGridPresenter(gridPresenter);
You must implement this method, otherwise runtime exception happens.
setAdapter
Usage of setAdapter is similar to BrowseFragment, but it’s more easy.
Prepare instance of ArrayObjectAdapter adapter, by specifying Presenter Ex. prepare mAdapter which uses CardPresenter
private ArrayObjectAdapter mAdapter;
mAdapter = new ArrayObjectAdapter(new CardPresenter());
Adding items (Model instance) to this adapter. Ex. adding Movie movie instance. mAdapter.add(movie);
After implementing setGridPresenter and setAdapter, VerticalGridFragment source code will be following.
package com.corochann.androidtvapptutorial.ui;
import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.VerticalGridPresenter;
import android.util.Log;
import com.corochann.androidtvapptutorial.data.VideoProvider;
import com.corochann.androidtvapptutorial.model.Movie;
import com.corochann.androidtvapptutorial.ui.presenter.CardPresenter;
import org.json.JSONException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* VerticalGridFragment shows contents with vertical alignment
*/
public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
private static final String TAG = VerticalGridFragment.class.getSimpleName();
private static final int NUM_COLUMNS = 4;
private LinkedHashMap<String, List<Movie>> mVideoLists = null;
private ArrayObjectAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
setupFragment();
}
private void setupFragment() {
VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
gridPresenter.setNumberOfColumns(NUM_COLUMNS);
setGridPresenter(gridPresenter);
mAdapter = new ArrayObjectAdapter(new CardPresenter());
/* Add movie items */
try {
mVideoLists = VideoProvider.buildMedia(getActivity());
} catch (JSONException e) {
Log.e(TAG, e.toString());
}
for (int i = 0; i < 3; i++) { // This loop is to for increasing the number of contents. not necessary.
for (Map.Entry<String, List<Movie>> entry : mVideoLists.entrySet()) {
// String categoryName = entry.getKey();
List<Movie> list = entry.getValue();
for (int j = 0; j < list.size(); j++) {
Movie movie = list.get(j);
mAdapter.add(movie);
}
}
}
setAdapter(mAdapter);
}
}
Even without implemeting setAdapter,VerticalGridFragment appears with no contents. But there is no meaning for it if there is no contents set in adapter.
setTitle
Showing a title on the top-right of VerticalGridFragment.
@Override
public void onCreate(Bundle savedInstanceState) {
...
setTitle("VerticalGridFragment");
setupFragment();
}
Instead of setTitle, you may also call setBadgeDrawable if you want to set Drawable at the title.
setOnItemViewSelectedListener
This is same with BrowseFrament, use a class which implements OnItemViewSelectedListener in the argument.
Ex.
setOnItemViewSelectedListener(new ItemViewSelectedListener());
private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
// write program here
}
}
setOnItemViewClickedListener
This is same with BrowseFrament, use a class which implements OnItemViewClickedListener in the argument.
Ex.
setOnItemViewClickedListener(new ItemViewClickedListener());
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
// write program here
}
}
setSelectedPosition
This method will change the current select focus to specified position.
The sample code after all the implementation is as follows
package com.corochann.androidtvapptutorial.ui;
import android.content.Intent;
import android.os.Bundle;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
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.VerticalGridPresenter;
import android.util.Log;
import com.corochann.androidtvapptutorial.data.VideoProvider;
import com.corochann.androidtvapptutorial.model.Movie;
import com.corochann.androidtvapptutorial.ui.background.PicassoBackgroundManager;
import com.corochann.androidtvapptutorial.ui.presenter.CardPresenter;
import org.json.JSONException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* VerticalGridFragment shows contents with vertical alignment
*/
public class VerticalGridFragment extends android.support.v17.leanback.app.VerticalGridFragment {
private static final String TAG = VerticalGridFragment.class.getSimpleName();
private static final int NUM_COLUMNS = 4;
private LinkedHashMap<String, List<Movie>> mVideoLists = null;
private ArrayObjectAdapter mAdapter;
private PicassoBackgroundManager picassoBackgroundManager;
@Override
public void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
picassoBackgroundManager = new PicassoBackgroundManager(getActivity());
setTitle("VerticalGridFragment");
//setBadgeDrawable(getResources().getDrawable(R.drawable.app_icon_your_company));
setupFragment();
setupEventListeners();
// it will move current focus to specified position. Comment out it to see the behavior.
// setSelectedPosition(5);
}
private void setupFragment() {
VerticalGridPresenter gridPresenter = new VerticalGridPresenter();
gridPresenter.setNumberOfColumns(NUM_COLUMNS);
setGridPresenter(gridPresenter);
mAdapter = new ArrayObjectAdapter(new CardPresenter());
/* Add movie items */
try {
mVideoLists = VideoProvider.buildMedia(getActivity());
} catch (JSONException e) {
Log.e(TAG, e.toString());
}
for (int i = 0; i < 3; i++) { // This loop is to for increasing the number of contents. not necessary.
for (Map.Entry<String, List<Movie>> entry : mVideoLists.entrySet()) {
// String categoryName = entry.getKey();
List<Movie> list = entry.getValue();
for (int j = 0; j < list.size(); j++) {
Movie movie = list.get(j);
mAdapter.add(movie);
}
}
}
setAdapter(mAdapter);
}
private void setupEventListeners() {
setOnItemViewClickedListener(new ItemViewClickedListener());
setOnItemViewSelectedListener(new ItemViewSelectedListener());
}
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
if (item instanceof Movie) {
Movie movie = (Movie) item;
Intent intent = new Intent(getActivity(), DetailsActivity.class);
intent.putExtra(DetailsActivity.MOVIE, movie);
getActivity().startActivity(intent);
}
}
}
private final class ItemViewSelectedListener implements OnItemViewSelectedListener {
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
picassoBackgroundManager.updateBackgroundWithDelay(((Movie) item).getBackgroundImageUrl());
}
}
}
Launch from MainFragment
Launch VerticalGridActivity from MainFragment. I added grid item to launch VerticalGridActivity, below is the added part.
private static final String GRID_STRING_VERTICAL_GRID_FRAGMENT = "VerticalGridFragment";
...
private final class ItemViewClickedListener implements OnItemViewClickedListener {
@Override
public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
...
} else if (item instanceof String){
...
} else if (item == GRID_STRING_VERTICAL_GRID_FRAGMENT) {
Intent intent = new Intent(getActivity(), VerticalGridActivity.class);
startActivity(intent);
}
...
}
}
}
private void loadRows() {
...
gridRowAdapter.add(GRID_STRING_GUIDED_STEP_FRAGMENT);
gridRowAdapter.add(GRID_STRING_VERTICAL_GRID_FRAGMENT);
gridRowAdapter.add(GRID_STRING_RECOMMENDATION);
...
}
Build and run
When the number of contents increases VerticalGridFragment grows to the bottom, not to the right like BrowseFragment.
Leanback Launcher app shows multiple rows in Apps, Games bar if the number of icon is many (2 rows are used for “Apps” category in above picture). But original ListRow provided by Leanback support library only allows to show contents in one line. In this chapter, goal is to allow ListRow to show multiple rows in each header, by making customized class of ListRow (Model) and ListRowPresenter (Presenter).
Explanation: Dig in source code
ListRow(Model), ListRowView(View), ListRowPresenter(Presenter) is used for MVP architecture of ListRow. When you read the source code of ListRowPresenter, you can find member of HorizontalGridView in ViewHolder.
public static class ViewHolder extends RowPresenter.ViewHolder {
final HorizontalGridView mGridView;
...
public final HorizontalGridView getGridView() {
return mGridView;
}
}
This mGridView is the container for showing icon contents of ListRow. And Also, This HorizontalGridView supports a method setNumRows, to specify how many number of rows should be used for the layout of HorizontalGridView.
So we only need to call setNumRows method for mGridView, implementation is easy as follows.
Making CustomListRow
Create a new class, CustomListRow class, at model package. This is a subclass of ListRow, and the only one difference from the parent class is that it has a member called mNumRows which specifies the number of rows.
package com.corochann.androidtvapptutorial.model;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ObjectAdapter;
import com.corochann.androidtvapptutorial.ui.presenter.CustomListRowPresenter;
/**
* Used with {@link CustomListRowPresenter}, it can display multiple rows.
* Use {@link #setNumRows(int)} method to specify the number of rows, default 1.
*/
public class CustomListRow extends ListRow {
private static final String TAG = CustomListRow.class.getSimpleName();
private int mNumRows = 1;
public CustomListRow(HeaderItem header, ObjectAdapter adapter) {
super(header, adapter);
}
public CustomListRow(long id, HeaderItem header, ObjectAdapter adapter) {
super(id, header, adapter);
}
public CustomListRow(ObjectAdapter adapter) {
super(adapter);
}
public void setNumRows(int numRows) {
mNumRows = numRows;
}
public int getNumRows() {
return mNumRows;
}
}
Making CustomListRowPresenter
In the same way, create a new class called CustomListRowPresenter, at ui.presenter package. This is a subclass of ListRowPresenter, it calls ViewHolder‘s getGridView().setNumRows() method to apply the number of rows for mGridView.
package com.corochann.androidtvapptutorial.ui.presenter;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.RowPresenter;
import com.corochann.androidtvapptutorial.model.CustomListRow;
/**
* Custom {@link #ListRowPresenter}, it can have multiple rows.
*
* Detail: Internally it is changing {@link ListRowPresenter.ViewHolder}'s
* {@link ListRowPresenter.ViewHolder#mGridView} to set number of rows.
*/
public class CustomListRowPresenter extends ListRowPresenter {
private static final String TAG = CustomListRowPresenter.class.getSimpleName();
public CustomListRowPresenter() {
super();
}
@Override
protected void onBindRowViewHolder(RowPresenter.ViewHolder holder, Object item) {
/* This two line codes changes the number of rows of ListRow */
int numRows = ((CustomListRow) item).getNumRows();
((ListRowPresenter.ViewHolder) holder).getGridView().setNumRows(numRows);
super.onBindRowViewHolder(holder, item);
}
@Override
protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
super.initializeRowViewHolder(holder);
/* Disable Shadow */
// setShadowEnabled(false);
}
}
Shadow customization
If you want to change the shadow effect for ListRow, you can modify Presenter class. setShadowEnabledmethod is used to enable/disable shadow effect.
You can try uncommenting above source code to see the effect.
Modification in MainFragment
Remaining task is to use CustomListRow and CustomListRowPresenter in MainFragment.
To set Presenter, you can specify it in the constructor of rows adapter
private void setRows() {
mRowsAdapter = new ArrayObjectAdapter(new CustomListRowPresenter()); // Initialize
...
/* Set */
setAdapter(mRowsAdapter);
}
Change ListRow to CustomListRow, and set the number of rows.
private CustomListRow mGridItemListRow;
mGridItemListRow = new CustomListRow(gridItemPresenterHeader, gridRowAdapter);
Just to note. In the AOSP source code, I could find ListRowHoverCardViewclass. There is also a javadoc comment in ListRowPresenter which says
* <h3>Hover card</h3> * Optionally, {@link #setHoverCardPresenterSelector(PresenterSelector)} can be used to * display a view for the currently focused list item below the rendered * list. This view is known as a hover card
However, I couldn’t find how to use it. Maybe its implementation is not complete?
More customization of BrowseFragment – Showing icon beside the Header title
The basic implementation of BrowseFragment is already explained in this tutorial. I’m going to proceed some advanced customization in following chapters.
Aim is to change the design of header, to show icons on the left of header title text, as shown in the above image.
How to customize header design
Leanback support library adopts MVP (Model-View-Presenter) architecture*, and HeaderItem (Model) is not an exception. We can prepare our own View and Presenter to customize the design of header. * MVP is explained in Chapter 14.
Step is as follows,
Make own Model (If necessary): Java class which extends HeaderItem.
Make own View: xml layout resource file
Make own Presenter: Java class which extends RowHeaderPresenter, to use own view.
Call setHeaderPresenterSelector method to specify which Presenter to be used.
Let’s take a look one by one.
Making own Model
HeaderItem class is used for Header model as default, however it has only 2 members id and name. We want to show image icon so we need a new class IconHeaderItem class to hold an image resource id.
Create new Java class IconHeaderItemin model package, extend HeaderItem class and declare a member mImageResId.
package com.corochann.androidtvapptutorial.model;
import android.support.v17.leanback.widget.HeaderItem;
import com.corochann.androidtvapptutorial.ui.presenter.IconHeaderItemPresenter;
/**
* Subclass of {@link HeaderItem} to hold icon resource id
* to show icon on header with {@link IconHeaderItemPresenter}
*/
public class IconHeaderItem extends HeaderItem {
private static final String TAG = IconHeaderItem.class.getSimpleName();
public static final int ICON_NONE = -1;
/** Hold an icon resource id */
private int mIconResId = ICON_NONE;
public IconHeaderItem(long id, String name, int iconResId) {
super(id, name);
mIconResId = iconResId;
}
public IconHeaderItem(long id, String name) {
this(id, name, ICON_NONE);
}
public IconHeaderItem(String name) {
super(name);
}
public int getIconResId() {
return mIconResId;
}
public void setIconResId(int iconResId) {
this.mIconResId = iconResId;
}
}
Icon resource id can be set at the constructor of IconHeaderItem. It is also allowed to not specify icon resource id, if you don’t want to show icon.
Making own View
Create a layout file called “icon_header_item.xml”. (Right click on res/layout → [New] > [File] → type “icon_header_item.xml” and press [OK].
<?xml version="1.0" encoding="utf-8"?>
<!--
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.
-->
<android.support.v17.leanback.widget.NonOverlappingLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/header_icon"
android:layout_width="24dp"
android:layout_height="24dp" />
<TextView
android:id="@+id/header_label"
android:layout_marginTop="6dp"
android:layout_width="140dp"
android:layout_height="wrap_content" />
</android.support.v17.leanback.widget.NonOverlappingLinearLayout>
Model: IconHeaderItem instance is obtained at onBindViewHolder, and resource icon id is converted to Drawable to set image on iconView.
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) {
IconHeaderItem iconHeaderItem = (IconHeaderItem) ((ListRow) o).getHeaderItem();
View rootView = viewHolder.view;
ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
int iconResId = iconHeaderItem.getIconResId();
if( iconResId != IconHeaderItem.ICON_NONE) { // Show icon only when it is set.
Drawable icon = rootView.getResources().getDrawable(iconResId, null);
iconView.setImageDrawable(icon);
}
...
}
Implementation in MainFragment
Now we’ve done for preparing MVP of header class. Remaining task is update MainFragment. setHeaderPresenterSelector method is used to specify Presenter class to be used for Header of BrowseFragment.
private void setupUIElements() {
...
setHeaderPresenterSelector(new PresenterSelector() {
@Override
public Presenter getPresenter(Object o) {
return new IconHeaderItemPresenter();
}
});
}
Change our header model from HeaderItem to IconHeaderItem.
Icon images are downloaded from Material icon library. (Maybe icon and header title is not matching, but this is just workaround for developing practice)
Build and run
You can see icon on the left of header title if successful.
By modifying icon_header_item.xml, you can easily customize header’s layout.
Kodi is originally developed as XBMC (Xbox Media Center). This is a really powerful and customizable application to enjoy playing videos, music, pictures, games, and more. Kodi really can do anything as “Media Center”, but it is so highly customizable that it looks difficult to use it for the beginners.
Kodi is open source (GPL), and runs on cross platform such as Windows, Mac, Linux, Android, i OS etc. Android app is also available with Android TV devices.
Feature Overview
This video is an example to look & feel what you can do after setting up Kodi.
The Kodi has many “add-ons” you can install, and by installing add-ons you can enjoy more functionality. For example, “Genesis” add-on introduced in the video helps you to automatically search various video contents (TV shows, Movies etc) on the internet.
You need a several initial set up to enjoy these add-ons. Follow the steps below.
Kodi is available by XBMC Foundation on Google play store for Android TV. Search with the word “XBMC” when you could not find with the word “kodi”.
Initial set up
After doing some initial setups, you can find video/music conveniently.
For the set up, I will just introduce a web site. Follow the steps introduced in Install Unofficial Kodi Addons. It shows tprecise steps with screenshots so it is very easy to follow.
Here is a summary for the steps. For beginners, install “fusion installer” and “config wizard” is enough which helps you to install package of addons to start with.
Fusion Installer
At Main menu, [SYSTEM] > [File manager]
[Add source]
Add Files source
Select <None> box and type “http://fusion.tvaddons.ag” then press [Done].
Select a field under “Enter a name for the media Source” and type “fusion” then press [Done].
Press [OK]
After confirming that “fusion” is listed in File manager window, go back to main menu
Install Config wizard, Addon Installer, and Fresh start addons using Fusion installer
[SYSTEM] > [Settings]
[Add-ons]
[Install from zip file]
Now you can find “fusion” in the list.
Select fusion, and you can find three directories called
start-here
xbmc-repos
xbmc-scripts
We are interested in “start-here” folder for easy initial setup.
After selecting “start-here”, you can find three addons which can be installed.
Select each of them to install Addon Installer, fresh start & Config wizard add-ons. Especially, config wizard add-ons automatically set up Kodi including package of other add-ons install. It takes several minutes to complete downloading/updating package of addons.
Done! Now you can see several addon images, e.g. Genesis, Stream All The Sources, 1 Channel, Icefilms, Phoenix in VIDEOS category and Radio,, Apple iTunes Podcasts, XBMC karaoke addon, Broadcastify, SoundCloud in MUSIC category, in main menu.
Review
Kodi seems for those who are well aware of technologies, since there are so many features you can customize. But once you could start using Kodi, it can do almost everything related to media streaming and your life definitely becomes one more step convenient. For example watching movie or TV shows, listening musics, watching beautiful pictures uploaded on Flickr etc. Even though Kodi runs on cross platform, it specially matches with TV devices. So if you have Android TV, try! try using it!
In the previous chapter, Background data loading – Android TV application hands on tutorial 15, I introduced LoaderManager and Loader class which helps to load/prepare (maybe time consuming) data in background. One of the example of “time consuming” data preparation is loading data from network. If you can provide the data from the web, app can always show updated, latest information.
(NOTE: the sample app works correctly only when your Android TV is connected to the internet from this chapter.)
Video data preparation
By proceeding to show video contents data dynamically from web, I changed the video source. I was using the video contents from PEXELS VIDEOS, this is public domain videos so that we can use it freely.
Until this chapter, I was preparing video data using MovieProvider class. It prepares Movie items in hard-coded way. Instead, we want to prepare data in more organized way and JSON format is used to prepare video data. This is the real video list data in JSON format.
I don’t cover JSON format itself in detail in this post. For those who are not familiar with JSON yet, I will put some of the links how to parse JSON below.
It is not so difficult, and you may also get feeling by looking this post’s sample code for how to parse JSON data. JSON data is consisting of either JSONObject or JSONArray (this relation is similar to the variable and array in usual program language).
There are some useful JSON analyze tool on the web like below
You can try copy and paste this JSON to visually understand what kind of data structure it has, which helps you to understand how you can parse JSON data more easily.
Data loading trigger – VideoItemLoader
Let’s proceed to implementation. As explained in previous chapter, data preparation is now done in loadInBackground method in VideoItemLoader.
Now we want to modify this method to prepare data from the web. * It may take some time for loading data, which is suitable to do it in background! This is the purpose that we introduced Loader in previous chapter.
Before that modify loadInBackground method as follows. New class VideoProvider is introduced here for the web data loading.
@Override
public LinkedHashMap<String, List<Movie>> loadInBackground() {
Log.d(TAG, "loadInBackground");
/*
* Executed in background thread.
* Prepare data here, it may take long time (Database access, URL connection, etc).
* return value is used in onLoadFinished() method in Activity/Fragment's LoaderCallbacks.
*/
//LinkedHashMap<String, List<Movie>> videoLists = prepareData();
LinkedHashMap<String, List<Movie>> videoLists = null;
try {
videoLists = VideoProvider.buildMedia(getContext());
} catch (JSONException e) {
Log.e(TAG, "buildMedia failed", e);
//cancelLoad();
}
return videoLists;
}
As you can see VideoLoader class only triggered to do the video data loading. Real loading procedure is done in VideoItemProvider class.
Data loading process – VideoItemProvider
Create a java new class called VideoItemProvider in com.corochann.androidtvapptutorial.data package. Below is the whole source code of this class.
package com.corochann.androidtvapptutorial.data;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.util.Log;
import com.corochann.androidtvapptutorial.model.Movie;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
*
*/
public class VideoProvider {
private static final String TAG = VideoProvider.class.getSimpleName();
public static final String VIDEO_LIST_URL = "https://raw.githubusercontent.com/corochann/AndroidTVappTutorial/master/app/src/main/assets/video_lists.json";
public static final String PREFIX_URL = "http://corochann.com/wp-content/uploads/2015/11/";
private static String TAG_ID = "id";
private static String TAG_MEDIA = "videos";
private static String TAG_VIDEO_LISTS = "videolists";
private static String TAG_CATEGORY = "category";
private static String TAG_STUDIO = "studio";
private static String TAG_SOURCES = "sources";
private static String TAG_DESCRIPTION = "description";
private static String TAG_CARD_THUMB = "card";
private static String TAG_BACKGROUND = "background";
private static String TAG_TITLE = "title";
private static LinkedHashMap<String, List<Movie>> sMovieList;
private static Resources sResources;
private static Uri sPrefixUrl;
public static void setContext(Context context) {
if (null == sResources) {
sResources = context.getResources();
}
}
/**
* It may return null when data is not prepared yet by {@link #buildMedia}.
* Ensure that data is already prepared before call this function.
* @return
*/
public static LinkedHashMap<String, List<Movie>> getMedia() {
return sMovieList;
}
/**
* ArrayList of movies within specified "category".
* If argument is null, then returns all movie list.
* @param category
* @return
*/
public static ArrayList<Movie> getMovieItems(String category) {
if(sMovieList == null) {
Log.e(TAG, "sMovieList is not prepared yet!");
return null;
} else {
ArrayList<Movie> movieItems = new ArrayList<>();
for (Map.Entry<String, List<Movie>> entry : sMovieList.entrySet()) {
String categoryName = entry.getKey();
if(category !=null && !category.equals(categoryName)) {
continue;
}
List<Movie> list = entry.getValue();
for (int j = 0; j < list.size(); j++) {
movieItems.add(list.get(j));
}
}
if(movieItems == null) {
Log.w(TAG, "No data foud with category: " + category);
}
return movieItems;
}
}
public static LinkedHashMap<String, List<Movie>> buildMedia(Context ctx) throws JSONException{
return buildMedia(ctx, VIDEO_LIST_URL);
}
public static LinkedHashMap<String, List<Movie>> buildMedia(Context ctx, String url)
throws JSONException {
if (null != sMovieList) {
return sMovieList;
}
sMovieList = new LinkedHashMap<>();
//sMovieListById = new HashMap<>();
JSONObject jsonObj = parseUrl(url);
if (null == jsonObj) {
Log.e(TAG, "An error occurred fetching videos.");
return sMovieList;
}
JSONArray categories = jsonObj.getJSONArray(TAG_VIDEO_LISTS);
if (null != categories) {
final int categoryLength = categories.length();
Log.d(TAG, "category #: " + categoryLength);
long id;
String title;
String videoUrl;
String bgImageUrl;
String cardImageUrl;
String studio;
for (int catIdx = 0; catIdx < categoryLength; catIdx++) {
JSONObject category = categories.getJSONObject(catIdx);
String categoryName = category.getString(TAG_CATEGORY);
JSONArray videos = category.getJSONArray(TAG_MEDIA);
Log.d(TAG,
"category: " + catIdx + " Name:" + categoryName + " video length: "
+ (null != videos ? videos.length() : 0));
List<Movie> categoryList = new ArrayList<Movie>();
Movie movie;
if (null != videos) {
for (int vidIdx = 0, vidSize = videos.length(); vidIdx < vidSize; vidIdx++) {
JSONObject video = videos.getJSONObject(vidIdx);
String description = video.getString(TAG_DESCRIPTION);
JSONArray videoUrls = video.getJSONArray(TAG_SOURCES);
if (null == videoUrls || videoUrls.length() == 0) {
continue;
}
id = video.getLong(TAG_ID);
title = video.getString(TAG_TITLE);
videoUrl = PREFIX_URL + getVideoSourceUrl(videoUrls);
bgImageUrl = PREFIX_URL + video.getString(TAG_BACKGROUND);
cardImageUrl = PREFIX_URL + video.getString(TAG_CARD_THUMB);
studio = video.getString(TAG_STUDIO);
movie = buildMovieInfo(id, categoryName, title, description, studio,
videoUrl, cardImageUrl, bgImageUrl);
categoryList.add(movie);
}
sMovieList.put(categoryName, categoryList);
}
}
}
return sMovieList;
}
private static Movie buildMovieInfo(long id,
String category,
String title,
String description,
String studio,
String videoUrl,
String cardImageUrl,
String bgImageUrl) {
Movie movie = new Movie();
movie.setId(id);
//movie.setId(Movie.getCount());
//Movie.incrementCount();
movie.setTitle(title);
movie.setDescription(description);
movie.setStudio(studio);
movie.setCategory(category);
movie.setCardImageUrl(cardImageUrl);
movie.setBackgroundImageUrl(bgImageUrl);
movie.setVideoUrl(videoUrl);
return movie;
}
// workaround for partially pre-encoded sample data
private static String getVideoSourceUrl(final JSONArray videos) throws JSONException {
try {
final String url = videos.getString(0);
return (-1) == url.indexOf('%') ? url : URLDecoder.decode(url, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new JSONException("Broken VM: no UTF-8");
}
}
protected static JSONObject parseUrl(String urlString) {
Log.d(TAG, "Parse URL: " + urlString);
BufferedReader reader = null;
//sPrefixUrl = Uri.parse(sResources.getString(R.string.prefix_url));
sPrefixUrl = Uri.parse(PREFIX_URL);
try {
java.net.URL url = new java.net.URL(urlString);
URLConnection urlConnection = url.openConnection();
reader = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream()));
//urlConnection.getInputStream(), "iso-8859-1"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
String json = sb.toString();
return new JSONObject(json);
} catch (Exception e) {
Log.d(TAG, "Failed to parse the json for media list", e);
return null;
} finally {
if (null != reader) {
try {
reader.close();
} catch (IOException e) {
Log.d(TAG, "JSON feed closed", e);
}
}
}
}
}
This class owns a static member LinkedHashMap<String, List<Movie>> sMovieList. It is the data that we want to download from web, and the loading is done in buildMedia method. This method will get the data in following procedure.
Get JSON data JSON data is obtained from the web, URL is defined as VIDEO_LIST_URL = "https://raw.githubusercontent.com/corochann/AndroidTVappTutorial/master/app/src/main/assets/video_lists.json" and parseUrl(String url) method, which is called beginning of buildMedia method, is accessing this URL and returns JSONObject.
Parse JSON data Parsing JSON data is done inside buildMedia method, by using getJSONObject, getJSONArray, getString, getInt methods etc which are methods of JSONObject &JSONArray class.You can compare with above code and this JSON checking by Online JSON Viewer to understand how the data is parsed. Note that JSONObject is enclosed by { }, and JSONArray is enclosed by [ ].
Construct Movie item and put it to sMovieList At the end of buildMedia method, movie instance is created from parsed data and is added to sMovieList.
Once you have launched the app, you can notice the video contents has changed from previous chapter’s implementation. This is of course because the data source is completely changed from hardcoded source to JSON formatted source on the web.
Now I can change/update video data anytime without any modification of JAVA source code.
Best data management architecture across application?
In previous chapter, I commented that Loader class is disappointing in the sense that Loader instance and its member cannot be shared among other Activities. We want to use video list data ontained from web. But accessing the internet to prepare data is time consuming and costly operation, we want to access web as less time as possible. So how we can manage data efficiently by reusing data among Activities? The implementation is originally done in Google’s sample source code and I just followed this implementation (and I try to explain its meaning here).
Since data should be independent from Activity, we can just have a class which handles/manages data. VideoProvider class, introduced in this chapter, is exactly doing this. There is a static member sMovieList declared as,
it is the data we have downloaded from the web. And once the data is downloaded, from next time of call of buildMedia method don’t access web but simply returns already created data
Loader class only triggers the timing of data loading, and the real data is managed by VideoProvider class. Its static instance can be referenced from all the activities in this application and we can access same data among activities.
This Tutorial was explaining about Leanback support library, which is usually used to show list of contents information. So developers may want to load a many meta data to show contents. When Activity or Fragment need to prepare big size of data, it is better to load the data in background.
For this purpose, we can use Loaderand LoaderManager. They are useful to implement background data loading followed by updating UI based on these loaded data. I’m going to implement this Loader in this chapter. To study about Loader, I found a very nice article to understand Loader and the LoaderManager in detail (from part 1 to part 4). This post is a basic course, so please refer the above article for advanced understanding.
As a summary of the article, the advantage of using LoaderManager & Loader is
Data loading process can be independent from Activity lifecycle.
LoaderManager is Activity unique instance (singleton), which handles Loader‘s lifecycle like starting, stopping, reseting. By using Loader, loading process can be independent from Activity lifecycle. One of the most useful situation is when configuration change occurs by display rotation (this is for Android phone and not for Android TV), Activity will be destroyed but Loader‘s information retains.
UI thread, background thread handling is easy.
This is very similar to AsyncTask so that we can asynchronously prepare data in background thread and get callback to update UI on UI thread. It enables us to develop “good” software architecture easily, so that we can reduce the task of UI thread. (See also AsyncTask usage summary.)
Loader is Event-driven, data handling is easy & convenient.
For example, when data source changed, we can get callback. We can easily update UI according to data change.
To understand implementation we need to take care the relation between Activity/Fragment side and Loader side implementation.
Relationship between Activity, Loader and LoaderManager
LoaderManager is Activity unique instance, and NOT system/application-wise unique instance. Therefore, it is not possible to use a data prepared in Loader A of Activity A from Activity B.
Hands on example: Minimum implementation
Let’s take a look at example of implementing Loader in MainFragment class. We need to implement 2 modules, Loader side which takes care of background data loading process and Activity side with LoaderManager.LoaderCallbacks which handles how to show data on UI.
Activity/Fragment side
1. We will register loader by getLoaderManger().initLoader(id, args, callback) where
id: Loader’s id. This should be identical in each Activity, it is ok to use same id number in different Activity because LoaderManager instance exists for each Activity/Fragment.
2. Implementation of LoaderManager.LoaderCallbacks, we need to override 3 methods.
onCreateLoader– We will instantiate Loader Here.
onLoadFinished– It will called when Loader finish preparing data, data will be shown on UI inside this method.
onLoaderReset– If data loading is unsuccessful or stops, this method will be called.
First, let’s implement initLoader in onActivityCreated and register LoaderManager.LoaderCallbacks. Then implement mock of Callbacks as follows.
private static final int VIDEO_ITEM_LOADER_ID = 1;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
getLoaderManager().initLoader(VIDEO_ITEM_LOADER_ID, null, new MainFragmentLoaderCallbacks());
...
}
private class MainFragmentLoaderCallbacks implements LoaderManager.LoaderCallbacks<HashMap<String, List<Movie>>> {
@Override
public Loader<HashMap<String, List<Movie>>> onCreateLoader(int id, Bundle args) {
/* Create new Loader */
Log.d(TAG, "VideoItemLoader: onCreateLoader");
return new VideoItemLoader();
}
@Override
public void onLoadFinished(Loader<HashMap<String, List<Movie>>> loader, HashMap<String, List<Movie>> data) {
Log.d(TAG, "VideoItemLoader: onLoadFinished");
/* Loader data has prepared. Start updating UI here */
}
@Override
public void onLoaderReset(Loader<HashMap<String, List<Movie>>> loader) {
Log.d(TAG, "VideoItemLoader: onLoadReset");
/* When it is called, Loader data is now unavailable due to some reason. */
}
}
return value of onCreateLoader method will be the instance of Loader. Now we are going to implement Loader side.
Loader side
The Loader is specified as return value of onCreateLoader. I Created new class VideoItemLoader in data package, and extend AsyanTaskLoader<D> to start with.
At least 2 methods need to be implemented (Override).
Constructor – parent class, AsyncTaskLoader, requires context. Pass it in the constructor.
loadInBackground() – this is just like doInBackground() method of AsyncTask.
What you “must” implement is above two methods. But when I try it, loadInBackground() is never called with this implementation. I needed to implement forceLoad() method in
onStartLoading()
to explicitly start Loading. Below is the code of these three methods implementation.
/**
* Loader class which prepares Movie class data
*/
public class VideoItemLoader extends AsyncTaskLoader<LinkedHashMap<String, List<Movie>>> {
private static final String TAG = VideoItemLoader.class.getSimpleName();
public VideoItemLoader(Context context) {
super(context);
}
@Override
public LinkedHashMap<String, List<Movie>> loadInBackground() {
Log.d(TAG, "loadInBackground");
/*
* Executed in background thread.
* Prepare data here, it may take long time (Database access, URL connection, etc).
* return value is used in onLoadFinished() method in Activity/Fragment's LoaderCallbacks.
*/
LinkedHashMap<String, List<Movie>> videoLists = prepareData();
return videoLists;
}
@Override
protected void onStartLoading() {
//super.onStartLoading();
forceLoad();
}
private LinkedHashMap<String, List<Movie>> prepareData() {
LinkedHashMap<String, List<Movie>> videoLists = new LinkedHashMap<>();
List<Movie> videoList = MovieProvider.getMovieItems();
videoLists.put("category 1", videoList);
videoLists.put("category 2", videoList);
videoLists.put("category 3", videoList);
return videoLists;
}
}
Data loading process has implemented. Finally, go back to Activity side to handle this data to show on UI.
Go back to Loader side
The last remaining task is to only implementing onLoadFinished callback. The argument is the loaded data coming from Loader.
@Override
public void onLoadFinished(Loader<LinkedHashMap<String, List<Movie>>> loader, LinkedHashMap<String, List<Movie>> data) {
Log.d(TAG, "onLoadFinished");
/* Loader data has prepared. Start updating UI here */
switch (loader.getId()) {
case VIDEO_ITEM_LOADER_ID:
Log.d(TAG, "VideoLists UI update");
/* Hold data reference to use it for recommendation */
mItems = new ArrayList<Movie>();
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
int index = 0;
/* GridItemPresenter */
HeaderItem gridItemPresenterHeader = new HeaderItem(index, "GridItemPresenter");
index++;
GridItemPresenter mGridPresenter = new GridItemPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add(GRID_STRING_ERROR_FRAGMENT);
gridRowAdapter.add(GRID_STRING_GUIDED_STEP_FRAGMENT);
gridRowAdapter.add(GRID_STRING_RECOMMENDATION);
gridRowAdapter.add(GRID_STRING_SPINNER);
mRowsAdapter.add(new ListRow(gridItemPresenterHeader, gridRowAdapter));
/* CardPresenter */
CardPresenter cardPresenter = new CardPresenter();
if (null != data) {
for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
ArrayObjectAdapter cardRowAdapter = new ArrayObjectAdapter(cardPresenter);
List<Movie> list = entry.getValue();
for (int j = 0; j < list.size(); j++) {
Movie movie = list.get(j);
cardRowAdapter.add(movie);
mItems.add(movie); // Add movie reference for recommendation purpose.
}
HeaderItem header = new HeaderItem(index, entry.getKey());
index++;
mRowsAdapter.add(new ListRow(header, cardRowAdapter));
}
} else {
Log.e(TAG, "An error occurred fetching videos");
}
/* Set */
setAdapter(mRowsAdapter);
}
}
Build and run
You can check the source code until here on github, but nothing much changed on UI since loader will do loading process in background. Loader‘s advantage appears when we need a background data loading process which may take long time. As one example of background data loading, I will introduce data loading from the Internet in Next chapter.
Comments: Loader in another Activity
We have implemented Loader in MainFragment, and how about implementing Loader in another Activity/Fragment, for example VideoDetailsFragment. I thought it is nice if we could use same Loader instance with different Activity. Indeed both MainFragment and VideoDetailsFragment need same VideoList data! However it is very disappointing fact for me that we cannot share the instance of Loader among Activity/Fragment, since LoaderManager is not a system-wise singleton, but the instance (LoaderManagerImpl mLoaderManager) exists in each Activity/Fragment.
Then how should we manage data among between Activity? I think one way is to make an independent data manager class which keeps the instance of data. This is also done in next chapter.
When I’m doing personal project, or making some documents, it always bother me how to find out contents (icons, videos, photos/pictures, musics) in accordance with copy right issues.
However, you don’t need to worry about copy right issues with contents under CC0(public domain) license. Here, I will list up some links which is under Creative Commons license.
Videos/Photos
When I Google with “cc0 video”, it shows several web services which is providing CC0 lisenced videos.
PEXELS VIDEOS, PEXELS Short scenery videos are provided in this site, the video is categorized so that you can look through the web site easily. The videos in this site is all CC0 license.
PLiXS Similar to PEXELS, you can find only CC0 licensed Videos/Photos. The contents here is high quality, not the selection of user’s
I like the quality of above 2 web sites, top featured image is also uploaded in PLiXS. But the number of videos are a little bit few. Below links are other sites you can look at.
Flickr You can find the videos posted by users with CC0 license. You can find variety of contents but they are not organized. So it may be difficult to find your desired content, but you can explore to find out “treasure” contents for you.
Music
Same way, there are some sites that is introducing free usage musics.
Creative Commons is one of the famous license for creative products that is to declare the content is open to use under some conditions. There are several type of conditions, see official explanation for license details.
For example, license called CC BY-SA requires you below 2 conditions
Attribution: you must give appropriate credit to original material.
ShareAlike: you must distribute your material under the same license.
In other words, you can use material by just following above conditions (commercial usage is also allowed).
Most “open”, dedicated license type is “pulblic domain”, also known as “CC0“. Its meaning is “No Rights Reserved”, so you can use contents under CC0 freely. It is even free with commercial usage and not necessary to show attribution.
Serializable & Parcelable class can be used to pass object references from one Activity to the other Activity via Intent/Bundle. Your object must implement either Serializable or Parcelable in order to pass it via Intent.
Serializable is defined in JAVA language as a marker interface, so after implementing Serializable no extra method implementation is needed. It is very easy to use, but the performance is not good since serializing mechanism is decided by Java and it uses refelection.
Parcelable class is provided by Android platform, and its performance is much faster than Serializable (according to this blog, it is more than 10 times faster!!). This is because the serialization method is explicitly written in Parcelable class. It means you need to implement serialization method, following 4 methods/members, which is quite troublesome.
writeToParcel()
Constructor with argument Parcel
Parcelable.Creator<> CREATOR
createFromParcel()
newArray()
describeContents()
Binder and AIDL works with Parcelable.
Using Android studio plugin to Easily implement Parcelable class
1. Open class which you want to implement Parcelable, and Press [ALT+Insert] (cursor position doesn’t matter, plugin always insert automatically generated code at the bottom of class). You can select “Parcelable”.
2. Select fields to be parceled. Usually just press OK.
3. Done! below methods are automatically generated, so easy.