Slide 1

Slide 1 text

Developing for TV androidtv

Slide 2

Slide 2 text

C a r l o s M o t a 2 @cafonsomota [email protected]

Slide 3

Slide 3 text

TV 3

Slide 4

Slide 4 text

First black and white TV was sold in 1928 4

Slide 5

Slide 5 text

First color TV was sold in 1953 5

Slide 6

Slide 6 text

VCR was launched in 1972 6

Slide 7

Slide 7 text

First wave of Interactive TV - order Pizza from STB’s - 1994 (Full Service Network) 7

Slide 8

Slide 8 text

Several attempts in the middle… until Apple launches Apple TV in 2007 8

Slide 9

Slide 9 text

Mass acceptance of Smart TV’s on late 2000’s 9

Slide 10

Slide 10 text

Google launches Google TV in 2010 10

Slide 11

Slide 11 text

2014 Google redesigns Google TV and launches Android TV 11

Slide 12

Slide 12 text

12 *2014 Q3 study available at http://www.marketingcharts.com/television/are-young-people-watching-less-tv-24817/ • Average viewing 141h 19 mins/per month • >5B hours watched daily worldwide • Declined TV viewing among 12-64 age groups • Increased usage of smartphones for watching video TV

Slide 13

Slide 13 text

TV Android 13

Slide 14

Slide 14 text

14

Slide 15

Slide 15 text

15 apply  plugin:  ‘com.android.application’

Slide 16

Slide 16 text

16 apply  plugin:  ‘com.android.application’

Slide 17

Slide 17 text

17 apply  plugin:  ‘com.android.application’

Slide 18

Slide 18 text

18 apply  plugin:  ‘com.android.application’

Slide 19

Slide 19 text

androidtv UI/UX 19

Slide 20

Slide 20 text

20 compileSdkVersion  21 • Distance to the TV • Larger (much larger) screens • Always connected to the internet • There’s no screen rotation (always landscape) • Not touchable • Use of remote control

Slide 21

Slide 21 text

21 compileSdkVersion  21 Accessibility

Slide 22

Slide 22 text

22 compileSdkVersion  21 Accessibility It’s not a tablet

Slide 23

Slide 23 text

23 compileSdkVersion  21 Accessibility It’s not a tablet It’s not a tablet on landscape

Slide 24

Slide 24 text

24 compileSdkVersion  21 Accessibility It’s not a tablet It’s not a tablet on landscape It’s not touchable

Slide 25

Slide 25 text

25 compileSdkVersion  21

Slide 26

Slide 26 text

                                                example.xml define how D-Pad navigation should work components most be focusable (default = true)

Slide 27

Slide 27 text

androidtv Develop an app from scratch 27

Slide 28

Slide 28 text

28 Demo mdevcon

Slide 29

Slide 29 text

29 compile  fileTree(dir:  ‘libs’,  include:  [‘*.jar’]) ListView Text View Linear Layout Horizontal Scroll View Linear Layout Image View Text View Image View Linear Layout

Slide 30

Slide 30 text

30 compile  fileTree(dir:  ‘libs’,  include:  [‘*.jar’]) ListView Text View Linear Layout Horizontal Scroll View Linear Layout Image View Text View Image View Linear Layout Fragment A Fragment B

Slide 31

Slide 31 text

31 compile  fileTree(dir:  ‘libs’,  include:  [‘*.jar’]) ListView Text View Linear Layout Horizontal Scroll View Linear Layout Image View Text View Image View Linear Layout Fragment A Fragment B Handle D-Pad events Handle animations Handle transitions

Slide 32

Slide 32 text

32 could  not  execute  build

Slide 33

Slide 33 text

androidtv Develop an app from scratch 33

Slide 34

Slide 34 text

androidtv Leanback support library 34

Slide 35

Slide 35 text

35 compile  ‘com.android.support.leanback-­‐v17:+’ • TV-optimized experience • Defined architecture model (MVP) • Pre-built fragments for TV (minSDK=17) • Consistency across different android TV’s • Fast to develop • Higher Performance • Built-In (beautifully) animation library

Slide 36

Slide 36 text

                                                                                                                                                                                AndroidManifest.xml identifies app has been built for Android TV standard theme for TV’s app launcher (equivalent to android:icon) to be defined as an Android TV app

Slide 37

Slide 37 text

37 compile  ‘com.android.support.leanback-­‐v17:+’ Model Presenter View

Slide 38

Slide 38 text

38 compile  ‘com.android.support.leanback-­‐v17:+’ Model Presenter View { "mdevcon" : [{ "category" : "Speakers", "speakers" : [ { "description" : "Carlos is a strong believer that the answer to life, the universe and everything is 42…”, "card" : “avatar_cmota.jpg", "name" : “Carlos Mota", "company" : “WIT Software"}, …

Slide 39

Slide 39 text

39 compile  ‘com.android.support.leanback-­‐v17:+’ • Online • Local data • new CardPresenter() • new StringPresenter() • lb_image_card_view.xml • card_view_event.xml Model Presenter View { "mdevcon" : [{ "category" : "Speakers", "speakers" : [ { "description" : "Carlos is a strong believer that the answer to life, the universe and everything is 42…”, "card" : “avatar_cmota.jpg", "name" : “Carlos Mota", "company" : “WIT Software"}, …

Slide 40

Slide 40 text

40 compile  ‘com.android.support.leanback-­‐v17:+’ • BackgroudManager • BrowseFragment • DetailsFragment • ErrorFragment • HeadersFragment • PlaybackOverlayFragment • RowsFragment • SearchFragment • VerticalGridFragment • …

Slide 41

Slide 41 text

BrowseFragment BrowseFragment PlaybackControlFragment DetailsFragment SearchFragment

Slide 42

Slide 42 text

42 • Multi-pane layout • Standard navigational design • Polished transitions between views • Quickly browse across different content

Slide 43

Slide 43 text

43 ListRowPresenter ArrayObjectAdapter

Slide 44

Slide 44 text

44 ArrayObjectAdapter ListRow (HeaderItem, ArrayObjectAdapter) HeaderItem

Slide 45

Slide 45 text

public  class  MainBrowseFragment  extends  BrowseFragment  {   !   ...            @Override        public  void  onActivityCreated(Bundle  savedInstanceState)  {   !      mRowsAdapter  =  new  ArrayObjectAdapter(new  ListRowPresenter());   !      ...            /*  Speakers  */            HeaderItem  headerSpeakers  =  new  HeaderItem(getString(R.string.header_speakers),null);   !          ArrayObjectAdapter  listRowSpeakerAdapter  =  new  ArrayObjectAdapter(new                                              CardPresenter(CARD_WIDTH,  CARD_HEIGHT));   !          for(Speaker  speaker  :  Data.getInstance().getSpeakersList())  {                    listRowSpeakerAdapter.add(speaker);            }   !          mRowsAdapter.add(new  ListRow(headerEventInfo,      listRowEventAdapter));            mRowsAdapter.add(new  ListRow(headerSponsorInfo,  listRowSponsorAdapter));            mRowsAdapter.add(new  ListRow(headerSpeakers,        listRowSpeakerAdapter));            mRowsAdapter.add(new  ListRow(headerPrevious,        listRowPreviousAdapter));   !          ...   !          setAdapter(mRowsAdapter); MainBrowseFragment.java custom presenter category name

Slide 46

Slide 46 text

46 CardPresenter

Slide 47

Slide 47 text

public  class  CardPresenter  extends  Presenter  {     ...   !        @Override          public  ViewHolder  onCreateViewHolder(final  ViewGroup  parent)  {                  ImageCardView  cardView  =  new  ImageCardView(parent.getContext())  {                          @Override                          public  void  setSelected(boolean  selected)  {                                  int  nBackground  =  parent.getContext().getResources().getColor(R.color.bg_normal);                                  int  sBackground  =  parent.getContext().getResources().getColor(R.color.bg_selected);                                  findViewById(R.id.info_field).setBackgroundColor(selected  ?  sBackground  :  nBackground);                                  super.setSelected(selected);                          }                  };   !                cardView.setFocusable(true);                  cardView.setFocusableInTouchMode(true);                  return  new  ViewHolder(cardView);          }   !        @Override          public  void  onBindViewHolder(ViewHolder  viewHolder,  Object  item)  {                  ImageCardView  cardView  =  (ImageCardView)  viewHolder.view;                  cardView.setTitleText(item.getSpeakerName());                  cardView.setContentText(item.getSpeakerCompany());                  cardView.setMainImageDimensions(CARD_WIDTH,  CARD_HEIGHT);                  cardView.setMainImage(viewHolder.view.getResources().getDrawable(item.getSpeakerImage()));          }   !        @Override          public  void  onUnbindViewHolder(ViewHolder  viewHolder)  {                  ((ImageCardView)  viewHolder.view).setMainImage(null);          }   ! CardPresenter.java required for D-Pad focus remove image refs. for GC alternatively we could inflate our custom one

Slide 48

Slide 48 text

48 • Smooth transition across different elements • User-feedback • Provides a sense of continuity

Slide 49

Slide 49 text

public  class  MainBrowseFragment  extends  BrowseFragment  {   !   ...          @Override          public  void  onActivityCreated(Bundle  savedInstanceState)  {        ...                  setOnItemViewSelectedListener(new  ItemViewSelectedListener());          }   !        private  final  class  ItemViewSelectedListener  implements  OnItemViewSelectedListener  {                                    @Override                  public  void  onItemSelected(Presenter.ViewHolder  viewHolder,  Object  item,                           RowPresenter.rowViewHolder  rvH,  Row  row)  {   !                        if  (item  instanceof  Movie)  {                                  mBackgroundRefId  =  ((Movie)  item).getMovieImage();                          }  else  {                                  mBackgroundRefId  =  0;                                  Log.d(TAG,  "Default  instance  type:  "  +  item);                          }                          startBackgroundTimer();                  }          }   ! MainBrowseFragment.java change background according to selected movie DPAD_RIGHT, DPAD_LEFT, DPAD_DOWN, DPAD_UP

Slide 50

Slide 50 text

     private  void  startBackgroundTimer()  {                if  (mBackgroundTimer  !=  null)  {                        mBackgroundTimer.cancel();                        mBackgroundTimer  =  null;                }                mBackgroundTimer  =  new  Timer();                mBackgroundTimer.schedule(new  UpdateBackgroundTask(),  BACKGROUND_UPDATE_DELAY);        }        private  void  setBackground()  {                BackgroundManager  backgroundManager  =  BackgroundManager.getInstance(getActivity());                backgroundManager.attach(getActivity().getWindow());                if(mBackgroundRefId  >  0)  {                        backgroundManager.setDrawable(getResources().getDrawable(mBackgroundRefId));                }  else  {                        ColorDrawable  colorDrawable  =  new  ColorDrawable(getResources().getColor(R.color.bg_default));                        backgroundManager.setDrawable(colorDrawable);                }        }        private  class  UpdateBackgroundTask  extends  TimerTask  {                @Override                public  void  run()  {                        mHandler.post(new  Runnable()  {                                @Override                                public  void  run()  {                                        setBackground();                                        mBackgroundTimer.cancel();                                }                        });                }        }   MainBrowseFragment.java time between refreshes

Slide 51

Slide 51 text

51 • Show detailed (selected) information • Quick access to related data

Slide 52

Slide 52 text

52 DetailsOverviewRow DetailsOverviewRowPresenter Primary layout Additional Row ArrayObjectAdapter

Slide 53

Slide 53 text

53 DetailsOverviewRow Header (title) Subtitle Description Actions Preview (image)

Slide 54

Slide 54 text

     private  void  setUIComponentsForMovie(final  Movie  currMovie)  {              DetailsOverviewRowPresenter  rowPresenter  =  new  DetailsOverviewRowPresenter(new  DetailsPresenter());              rowPresenter.setOnActionClickedListener(new  OnActionClickedListener()  {                      @Override                      public  void  onActionClicked(Action  action)  {                              if(action.getId()  ==  ACTION_WATCH_TRAILER)  {                                      Intent  intent  =  new  Intent(getActivity(),  PlaybackActivity.class);                                      intent.putExtra(Movie.class.getSimpleName(),  currMovie);                                      startActivity(intent);                              }                      }              });              ClassPresenterSelector  selector  =  new  ClassPresenterSelector();              selector.addClassPresenter(DetailsOverviewRow.class,  rowPresenter);              selector.addClassPresenter(ListRow.class,  new  ListRowPresenter());              DetailsOverviewRow  detailsOverviewRow  =  new  DetailsOverviewRow(currMovie);              detailsOverviewRow.setImageDrawable(getResources().getDrawable(currMovie.getMovieImage()));              detailsOverviewRow.addAction(new  Action(ACTION_WATCH_TRAILER,  currMovie.getActionName()));              mRowsAdapter  =  new  ArrayObjectAdapter(selector);              mRowsAdapter.add(detailsOverviewRow);              ArrayObjectAdapter  listRowAdapter  =  new  ArrayObjectAdapter(new  CardPresenter());              for(Movie  movie  :  movieList)  {                      listRowAdapter.add(movie);              }              HeaderItem  header  =  new  HeaderItem(0,  getString(R.string.details_related),  null);              mRowsAdapter.add(new  ListRow(header,  listRowAdapter));              setAdapter(mRowsAdapter);        }   MediaDetailsFragment.java

Slide 55

Slide 55 text

55 • Video control commands • Additional actions

Slide 56

Slide 56 text

56 List Row Video View PlaybackControlRow MediaMetaData PlaybackOverlayFragment

Slide 57

Slide 57 text

57 ThumbsUpAction ArrayObjectAdapter SkipPreviewsAction SkipNextAction FastForwardAction RewindAction PlayPauseAction RepeatAction ShuffleAction ThumbsDownAction HighQualityAction CloseCaptionAction

Slide 58

Slide 58 text

     @Override        protected  void  onCreate(Bundle  savedInstanceState)  {                ...                mVideoView  =  (VideoView)  findViewById(R.id.vv_video);                mMediaSession  =  new  MediaSession(this,  getString(R.string.app_name));                mMediaSession.setCallback(new  MediaSessionCallback());                mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS  |                                                              MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);                ...          }          private  void  updateMetadata(final  Movie  movie)  {                final  MediaMetadata.Builder  metadataBuilder  =  new  MediaMetadata.Builder();                metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE,  movie.getMovieTitle());                metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,  movie.getMovieAuthor());                      Bitmap  image  =  BitmapFactory.decodeResource(getResources(),  movie.getMovieImage());                metadataBuilder.putBitmap(MediaMetadata.METADATA_KEY_ART,  image);                mMediaSession.setMetadata(metadataBuilder.build());        }   PlaybackControlFragment.java Media callbacks to load video when ready and notify user when is not possible to load update current MovieMetaData for “Now Playing” card

Slide 59

Slide 59 text

     private  void  addPlaybackControlsRow()  {                mPlaybackControlsRow  =  new  PlaybackControlsRow(mSelectedMovie);                mRowsAdapter.add(mPlaybackControlsRow);                ControlButtonPresenterSelector  presenterSelector  =  new  ControlButtonPresenterSelector();                mPrimaryActionsAdapter  =  new  ArrayObjectAdapter(presenterSelector);                mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);                mPlayPauseAction        =  new  PlayPauseAction(getActivity());                mSkipNextAction          =  new  PlaybackControlsRow.SkipNextAction(getActivity());                mSkipPreviousAction  =  new  PlaybackControlsRow.SkipPreviousAction(getActivity());                mFastForwardAction    =  new  PlaybackControlsRow.FastForwardAction(getActivity());                mRewindAction              =  new  PlaybackControlsRow.RewindAction(getActivity());                mPrimaryActionsAdapter.add(mSkipPreviousAction);                mPrimaryActionsAdapter.add(new  PlaybackControlsRow.RewindAction(getActivity()));                mPrimaryActionsAdapter.add(mPlayPauseAction);                mPrimaryActionsAdapter.add(new  PlaybackControlsRow.FastForwardAction(getActivity()));                mPrimaryActionsAdapter.add(mSkipNextAction);        }   PlaybackControlFragment.java

Slide 60

Slide 60 text

     private  void  setupRows()  {                PlaybackControlsRowPresenter  playbackControlsRowPresenter  =                         new  PlaybackControlsRowPresenter(new  DescriptionPresenter());                playbackControlsRowPresenter.setOnActionClickedListener(new  OnActionClickedListener()  {                        public  void  onActionClicked(Action  action)  {                                if  (action.getId()  ==  mPlayPauseAction.getId())  {                                        if  (mPlayPauseAction.getIndex()  ==  PlayPauseAction.PLAY)  {                                                startProgressAutomation();                                                mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),                                                                mPlaybackControlsRow.getCurrentTime(),  true);                                        }  else  {                                                stopProgressAutomation();                                                mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),                                                                mPlaybackControlsRow.getCurrentTime(),  false);                                        }                                }  else  if  (action.getId()  ==  mSkipNextAction.getId())  {                                        next();                                }                                  ...                ClassPresenterSelector  ps  =  new  ClassPresenterSelector();                ps.addClassPresenter(PlaybackControlsRow.class,  playbackControlsRowPresenter);                ps.addClassPresenter(ListRow.class,  new  ListRowPresenter());                mRowsAdapter  =  new  ArrayObjectAdapter(ps);                addPlaybackControlsRow();                addOtherRows();                setAdapter(mRowsAdapter);        }   PlaybackControlFragment.java

Slide 61

Slide 61 text

     private  void  next()  {                  if  (++mCurrentItem  >=  mItems.size())  {                          mCurrentItem  =  0;                  }   !                if  (mPlayPauseAction.getIndex()  ==  PlayPauseAction.PLAY)  {                          mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),  0,  false);                  }  else  {                          mCallback.onFragmentPlayPause(mItems.get(mCurrentItem),  0,  true);                  }   !                mFfwRwdSpeed  =  INITIAL_SPEED;                  updatePlaybackRow();          }   PlaybackControlFragment.java

Slide 62

Slide 62 text

     @Override        public  void  onFragmentPlayPause(Movie  movie,  int  position,  Boolean  playPause)  {                mVideoView.setVideoPath(movie.getMovieUrl());                if  (position  ==  0  ||  mPlaybackState  ==  LeanbackPlaybackState.IDLE)  {                        setupCallbacks();                        mPlaybackState  =  LeanbackPlaybackState.IDLE;                }                if  (playPause  &&  mPlaybackState  !=  LeanbackPlaybackState.PLAYING)  {                        mPlaybackState  =  LeanbackPlaybackState.PLAYING;                        if  (position  >  0)  {                                mVideoView.seekTo(position);                                mVideoView.start();                        }                }  else  {                        mPlaybackState  =  LeanbackPlaybackState.PAUSED;                        mVideoView.pause();                }                updatePlaybackState(position);                updateMetadata(movie);        }   PlaybackControlActivity.java

Slide 63

Slide 63 text

63 • ACTION_BOOT_COMPLETED for recommendations update

Slide 64

Slide 64 text

64 • ACTION_BOOT_COMPLETED for recommendations update • Equivalent to the notifications on smartphone

Slide 65

Slide 65 text

public  class  UpdateRecommendationsService  extends  IntentService  {   !        private  NotificationManager  mNotificationManager;   !        ...                  @Override          protected  void  onHandleIntent(Intent  intent)  {                  if  (mNotificationManager  ==  null)  {                          mNotificationManager  =  (NotificationManager)                                 getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE);                  }   !                RecommendationBuilder  builder  =  new  RecommendationBuilder()                                                                                                  .setContext(getApplicationContext())                                                                                                  .setSmallIcon(R.drawable.ic_video);   !                for  (Movie  movie  :  dailyRecommendedVideos())  {                          final  int  id  =  dailyRecommendedVideos.indexOf(movie);                          final  RecommendationBuilder  notificationBuilder  =  builder                                          .setId(id)                                          .setPriority(id)                                          .setTitle(movie.getMovieTitle())                                          .setDescription(movie.getMovieDescription())                                          .setIntent(buildPendingIntent(movie));   !                        Bitmap  image  =  BitmapFactory.decodeResource(getResources(),  movie.getMovieImage());                          notificationBuilder.setBitmap(image);                          Notification  notification  =  notificationBuilder.build();                          mNotificationManager.notify(id,  notification);                          }                  }          }   UpdateRecommendationsService.java wrapper for notification class

Slide 66

Slide 66 text

     public  RecommendationBuilder  setBackground(String  uri)  {                mBackgroundUri  =  uri;                return  this;        }        public  Notification  build()  {                Log.d(TAG,  "Building  notification  -­‐  "  +  this.toString());                Bundle  extras  =  new  Bundle();                if  (mBackgroundUri  !=  null)  {                        Log.d(TAG,  "Background  -­‐  "  +  mBackgroundUri);                        extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,  mBackgroundUri);                }                Notification  notification  =  new  NotificationCompat.BigPictureStyle(                                new  NotificationCompat.Builder(mContext)                                                .setContentTitle(mTitle)                                                .setContentText(mDescription)                                                .setPriority(mPriority)                                                .setLocalOnly(true)                                                .setOngoing(true)                                                .setColor(mContext.getResources().getColor(R.color.mdevcon_selected_background))                                                .setCategory(Notification.CATEGORY_RECOMMENDATION)                                                .setLargeIcon(mBitmap)                                                .setSmallIcon(mSmallIcon)                                                .setContentIntent(mIntent)                                                .setExtras(extras))                                .build();                return  notification;        }   RecommendationBuilder.java allows to define an image for the car presenter class can use as background

Slide 67

Slide 67 text

     private  PendingIntent  buildPendingIntent(Movie  movie)  {                Intent  detailsIntent  =  new  Intent(this,  MediaActivity.class);                detailsIntent.putExtra(Movie.class.getSimpleName(),  movie);                TaskStackBuilder  stackBuilder  =  TaskStackBuilder.create(this);                stackBuilder.addParentStack(MediaActivity.class);                stackBuilder.addNextIntent(detailsIntent);                detailsIntent.setAction(movie.hashCode()+"");                PendingIntent  intent  =  stackBuilder.getPendingIntent(0,  PendingIntent.FLAG_UPDATE_CURRENT);                return  intent;        }   UpdateRecommendationsService.java every notification should be unique, otherwise they will be replaced (updated)

Slide 68

Slide 68 text

68 • Fast access to content • Speech recognition

Slide 69

Slide 69 text

69 CardPresenter ListRowPresenter

Slide 70

Slide 70 text

public  class  SearchFragment  extends  android.support.v17.leanback.app.SearchFragment                  implements  android.support.v17.leanback.app.SearchFragment.SearchResultProvider  {   !        private  static  final  String  TAG  =  "SearchFragment";          private  static  final  int  SEARCH_DELAY_MS  =  300;   !        private  ArrayObjectAdapter  mRowsAdapter;          private  Handler  mHandler  =  new  Handler();          private  SearchRunnable  mDelayedLoad;   !        @Override          public  void  onCreate(Bundle  savedInstanceState)  {                  super.onCreate(savedInstanceState);   !                mRowsAdapter  =  new  ArrayObjectAdapter(new  ListRowPresenter());                  setSearchResultProvider(this);                  setOnItemClickedListener(getDefaultItemClickedListener());                  mDelayedLoad  =  new  SearchRunnable();          }   SearchFragment.java searching for items should not be done on UI level

Slide 71

Slide 71 text

       @Override          public  ObjectAdapter  getResultsAdapter()  {                  return  mRowsAdapter;          }          @Override          public  boolean  onQueryTextChange(String  newQuery)  {                  queryByWords(newQuery);                  return  true;          }          @Override          public  boolean  onQueryTextSubmit(String  query)  {                  queryByWords(query);                  return  true;          }          private  void  queryByWords(String  words)  {                  mRowsAdapter.clear();                  if  (!TextUtils.isEmpty(words))  {                          mDelayedLoad.setSearchQuery(words);                          mHandler.removeCallbacks(mDelayedLoad);                          mHandler.postDelayed(mDelayedLoad,  SEARCH_DELAY_MS);                  }          }   implements SearchResultProvider user pressed DPAD_CENTER real-time update on query

Slide 72

Slide 72 text

androidtv Pitfalls to avoid 72

Slide 73

Slide 73 text

73 versionName  “1.0” • Do not ‘lose’ focus • Provide easy shortcuts for common actions • Search within app • Minimum actions required policy • Acquire audio focus when playing content • Make use of visual indicators for more information • Compensate for overscan • Don’t try to reinvent the wheel • Eat your own dog food

Slide 74

Slide 74 text

Television is chewing for the eyes 74 Frank Lloyd Wright

Slide 75

Slide 75 text

TV How can I start? 75

Slide 76

Slide 76 text

76 Android Studio http://developer.android.com/sdk/index.html SDK Manager Install Android 5.0.1 (API 21) AVD Manager Create an Android TV virtual device Support Libraries leanback, recyclerview and cardview buildTools

Slide 77

Slide 77 text

https://github.com/googlesamples/androidtv-Leanback 77 Android Developers https://developer.android.com/training/tv/playback/index.html mdevcon https://play.google.com/store/apps/details?id=com.cmota.mdevcon dependencies GitHub @googlesamples YouTube - Google I/O 2014 - Android TV https://www.youtube.com/watch?v=y3dCUPeyhag

Slide 78

Slide 78 text

C a r l o s M o t a 78 @cafonsomota [email protected]

Slide 79

Slide 79 text

79 YouTube

Slide 80

Slide 80 text

80 TED

Slide 81

Slide 81 text

81 Showtime