Slide 1

Slide 1 text

Android TV S t r a n g e lo v e Or: How I STOP WORRYING AND LOVE THE BOMB LEARNED TO +Carlos Mota @cafonsomota

Slide 2

Slide 2 text

You’re not going to guess what happened on the latest episode of Game of Thrones… - (almost) everyone

Slide 3

Slide 3 text

3 5.5 hours/per day average viewing time 40 mins on mobile 25 mins on computer

Slide 4

Slide 4 text

4 5Billions hours watched daily worldwide

Slide 5

Slide 5 text

5 First black and white TV was sold in 1928

Slide 6

Slide 6 text

6 First color TV was sold in 1953

Slide 7

Slide 7 text

VCR was launched in 1972 7

Slide 8

Slide 8 text

Interactive TV: order Pizza - 1994 (Full Service Network) 8

Slide 9

Slide 9 text

Several attempts in the middle… until Apple TV in 2007 9

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

2014 Google redesigns Google TV and launches Android TV 11

Slide 12

Slide 12 text

12

Slide 13

Slide 13 text

androidtv

Slide 14

Slide 14 text

apply  plugin:  ‘com.android.application’

Slide 15

Slide 15 text

androidtv UI/UX

Slide 16

Slide 16 text

Distance to the TV Larger (way larger) screens Always connected to the internet No screen rotation (always landscape) Not touchable Use of remote control 16 compileSdkVersion  21

Slide 17

Slide 17 text

17 Accessibility compileSdkVersion  21

Slide 18

Slide 18 text

18 It’s not a tablet Accessibility compileSdkVersion  21

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

21 compileSdkVersion  21

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

androidtv Develop an app from scratch

Slide 24

Slide 24 text

Demo GDG  Porto

Slide 25

Slide 25 text

Demo GDG  Porto

Slide 26

Slide 26 text

ListView LinearLayout ImageView TextView LinearLayout Horizontal Scroll LinearLayout ImageView TextView Button 26

Slide 27

Slide 27 text

ListView LinearLayout ImageView TextView LinearLayout Horizontal Scroll LinearLayout ImageView TextView Button Fragment A Fragment B 27

Slide 28

Slide 28 text

ListView LinearLayout ImageView TextView LinearLayout Horizontal Scroll LinearLayout ImageView TextView Button Fragment A Fragment B transitions Handle animations Handle D-Pad events 28

Slide 29

Slide 29 text

10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:  FATAL  EXCEPTION:  main   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:  Process:   com.cmota.gdgportotv,  PID:  2055   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:   java.lang.OutOfMemoryError:  Failed  to  allocate  a  9114908  byte  allocation  with  6013044  free   bytes  and  5MB  until  OOM   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   dalvik.system.VMRuntime.newNonMovableArray(Native  Method)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.graphics.BitmapFactory.nativeDecodeAsset(Native  Method)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:988)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.content.res.Resources.loadDrawableForCookie(Resources.java:2474)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.content.res.Resources.loadDrawable(Resources.java:2381)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.content.res.Resources.getDrawable(Resources.java:787)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at   android.content.res.Resources.getDrawable(Resources.java:752)   10-­‐10  11:30:16.411  2055-­‐2055/com.cmota.gdgportotv  E/AndroidRuntime:          at  

Slide 30

Slide 30 text

Develop an app from scratch androidtv

Slide 31

Slide 31 text

Leanback support library androidtv

Slide 32

Slide 32 text

Guided Step Fragment Search Fragment Browse Fragment Details Fragment (full bleed) PlaybackOverlayFragment Details Fragment

Slide 33

Slide 33 text

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 compile  ‘com.android.support.leanback-­‐v17:+’ 33

Slide 34

Slide 34 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 35

Slide 35 text

35 compile  ‘com.android.support.leanback-­‐v17:+’ Model Presenter View Online Data Local Database System Database Touch/D-Pad events Voice events Movement events

Slide 36

Slide 36 text

Walkthrough multiple step flows Show previous steps Overlay over content Do not disturb the user 36

Slide 37

Slide 37 text

@Override   public  GuidanceStylist.Guidance  onCreateGuidance(Bundle  savedInstanceState)  {
        String  title  =  getString(R.string.guided_gdg_dev_fest);
        String  breadcrumb  =  getString(R.string.guided_welcome);
        String  description  =  getString(R.string.guided_select_your_gdg_home);
        Drawable  icon  =  getDrawable(R.drawable.gdg_devfest_logo);
 
        return  new  GuidanceStylist.Guidance(title,  description,  breadcrumb,  icon);
 }   @Override
 public  void  onCreateActions(List  actions,  Bundle  savedInstanceState)  {
        String[]  countries  =  ItemListWorldWide.WORLDWIDE_GDG;
        for(int  index=0;  index

Slide 38

Slide 38 text

@Override
 public  void  onGuidedActionClicked(GuidedAction  action)  {          Intent  intent  =  new  Intent(getActivity(),  MainActivity.class);          intent.putExtra(Values.EXTRA_ARG_COUNTRY,  action.getId());
        startActivity(new  Intent(getActivity(),  MainActivity.class));
        getActivity().finish();
 }   InitialGuidedStepsFragment  extends  GuidedStepsFragment

Slide 39

Slide 39 text

Multi-pane layout Standard navigational design Smooth transitions Navigate across different content Quick access to everything 39

Slide 40

Slide 40 text

40 ListRowPresenter ArrayObjectAdapter new  ArrayObjectAdapter(new  ListRowPresenter());

Slide 41

Slide 41 text

41 HeaderItem ListRow new  ListRow(headerEventInfo,  listRowEventAdapter)

Slide 42

Slide 42 text

       mRowsAdapter  =  new  ArrayObjectAdapter(new  ListRowPresenter());          /*  Speakers  */          HeaderItem  headerSpeakers  =  new  HeaderItem(getString(R.string.header_speakers),  null);          ArrayObjectAdapter  speakersAdapter  =  new  ArrayObjectAdapter(new  CardPresenter());          speakersAdapter.add(Data.getInstance().getSpeakersList());          ...          mRowsAdapter.add(new  ListRow(headerEventInfo,      eventsAdapter));          mRowsAdapter.add(new  ListRow(headerSponsorInfo,  sponsorsAdapter));          mRowsAdapter.add(new  ListRow(headerSpeakers,        speakersAdapter));          mRowsAdapter.add(new  ListRow(headerPrevious,        googleIOAdapter));          mRowsAdapter.add(new  ListRow(headerOther,              otherGDGAdapter));          setAdapter(mRowsAdapter); MainBrowserFragment  extends  BrowseFragment custom presenter category name and icon

Slide 43

Slide 43 text

43 CardPresenter new  ArrayObjectAdapter(new  CustomCardPresenter())

Slide 44

Slide 44 text

       @Override          public  ViewHolder  onCreateViewHolder(final  ViewGroup  parent)  {                  ImageCardView  cardView  =  new  ImageCardView(parent.getContext())  {                          @Override                          public  void  setSelected(boolean  selected)  {                                  int  nBackground  =  mContext.getResources().getColor(R.color.bg_normal);                                  int  sBackground  =  mContext..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);          }   CardPresenter  extends  Presenter required for D-Pad focus alternatively we could create a custom one

Slide 45

Slide 45 text

       @Override          public  ViewHolder  onCreateViewHolder(final  ViewGroup  parent)  {                  ...          }          @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  extends  Presenter remove image refs. for GC

Slide 46

Slide 46 text

Smooth transitions User-feedback Sense of continuity 46

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

time between refreshes MainBrowserFragment  extends  BrowseFragment        private  void  startBackgroundTimer()  {                  mBackgroundTimer  =  new  Timer();                  mBackgroundTimer.schedule(new  UpdateBackgroundTask(),  BACKGROUND_UPDATE_DELAY);          }          private  void  setBackground()  {                  BackgroundManager  backgroundManager  =  BackgroundManager.getInstance(getActivity());                  backgroundManager.attach(getActivity().getWindow());                  backgroundManager.setDrawable(getResources().getDrawable(mBackgroundRefId));          }          private  class  UpdateBackgroundTask  extends  TimerTask  {                  @Override                  public  void  run()  {                          mHandler.post(()  →  {                                          setBackground();                                  });                  }          }  

Slide 49

Slide 49 text

Detailed information Quick access to related data Full bleed mode 49

Slide 50

Slide 50 text

50 new  DetailsOverviewRowPresenter(dDPresenter)) new  FullWithDetailsOverviewRowPresenter ArrayObjectAdapter DetailsOverviewRowPresenter ListRowPresenter ClassPresenterSelector

Slide 51

Slide 51 text

51 new  ArrayObjectAdapter(classPresenterSelector())) Actions Description Subtitle Title AbstractDetailsDescriptionPresenter Preview Image ListRow

Slide 52

Slide 52 text

         ClassPresenterSelector  selector  =  new  ClassPresenterSelector();            selector.addClassPresenter(DetailsOverviewRow.class,  rowPresenter);            selector.addClassPresenter(ListRow.class,  new  ListRowPresenter());            DetailsOverviewRow  detailsOverviewRow  =  new  DetailsOverviewRow(item);            detailsOverviewRow.setImageDrawable(getResources().getDrawable(item.getMainImage()));            detailsOverviewRow.addAction(new  Action(ACTION_TWITTER,  item.getActionName()));            mRowsAdapter  =  new  ArrayObjectAdapter(selector);            mRowsAdapter.add(detailsOverviewRow);            ArrayObjectAdapter  listRowAdapter  =  new  ArrayObjectAdapter(new  CardPresenter());            for(Item  item  :  itemList)  {                    listRowAdapter.add(item);            }            HeaderItem  header  =  new  HeaderItem(0,  getString(R.string.details_related),  null);            mRowsAdapter.add(new  ListRow(header,  listRowAdapter));            setAdapter(mRowsAdapter); MediaDetailsFragment  extends  DetailsFragment

Slide 53

Slide 53 text

         DetailsOverviewRowPresenter  rowPresenter  =  new  DetailsOverviewRowPresenter(                                                                                                                                        new  DetailsPresenter());            rowPresenter.setOnActionClickedListener(new  OnActionClickedListener()  {                    @Override                    public  void  onActionClicked(Action  action)  {                            if(action.getId()  ==  ACTION_TWITTER)  {                                    Intent  intent  =  new  Intent(getActivity(),  WebActivity.class);                                    intent.putExtra(Values.ARG_EXTRA_ACTION,  item);                                    startActivity(intent);                            }                    }            });           MediaDetailsFragment  extends  DetailsFragment

Slide 54

Slide 54 text

Video control actions Quick access to related videos Integration with third parties 54

Slide 55

Slide 55 text

PlaybackOverlayFragment  extends  DetailsFragment 55

Slide 56

Slide 56 text

56 VideoView new  ArrayObjectAdapter(classPresenterSelector); PlaybackOverlayFragment ListRow PlaybackControlRow MediaMetadata PlayPauseAction FastForwardAction SkipNextAction ThumbsUpAction RepeatAction ShuffleAction …

Slide 57

Slide 57 text

mMediaSession  =  new  MediaSession(this,  getString(R.string.app_name));   mMediaSession.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS  |                                                MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS);   setSessionToken(mSession.getSessionToken());   ...   int  result  =   requestAudioFocus(afChangeListener,  AudioManager.STREAM_MUSIC,  AudioManager.AUDIOFOCUS_GAIN);   if(result  ==  AudioManager.AUDIOFOCUS_REQUEST_GRANTED)  {              if(!mMediaSession.isActive())  {                      mMediaSession.setActive(true);            }   }   public  void  registerCallback(MediaSessionCallback  cb)  {              mMediaSession.setCallback(cb);   }     MediaPlaybackService  extends  MediaBrowserService The card will be removed if the application calls setActive(false) or other app requires the audio focus

Slide 58

Slide 58 text

MediaSessionCallback  extends  MediaSession.Callback        @Override
        public  void  onPlay()  {
                playPause(true);                  }
        @Override
        public  void  onPlayFromMediaId(String  mediaId,  Bundle  extras)  {
                setVideoPath(mItemList.getItemById(mediaId).getLinkUrl());
                mPlaybackState  =  LeanbackPlaybackState.PAUSED;
                updateMetadata(mItemList.getItemById(mediaId));
                playPause(extras.getBoolean(Values.AUTO_PLAY));
        }
        @Override
        public  void  onSeekTo(long  pos)  {
                setPosition((int)  pos);
                mVideoView.seekTo(mPosition);
                updatePlaybackState();
        } Pause current video before starting new one UI Update (activity)

Slide 59

Slide 59 text

MediaSessionCallback  extends  MediaSession.Callback        @Override          public  void  onSkipToNext()  {
                PlaybackState.Builder  stateBuilder  =                                                  new  PlaybackState.Builder().setActions(getAvailableActions());
                stateBuilder.setState(PlaybackState.STATE_SKIPPING_TO_NEXT,  0,  1.0f);
                mMediaSession.setPlaybackState(stateBuilder.build());                  if  (++mCurrentItem  >=  mRelatedVideosList.size())  {                        mCurrentItem  =  0;
                }                  Bundle  bundle  =  new  Bundle();
                bundle.putBoolean(PlaybackOverlayActivity.AUTO_PLAY,  true);
                String  nextId  =  mRelatedVideos.get(mCurrentItem).getId());
                getMediaController().getTransportControls().playFromMediaId(nextId,  bundle);
        } Set media session to skip current video position in time and playback speed

Slide 60

Slide 60 text

MediaSessionCallback  extends  MediaSession.Callback private  void  updateMetadata(Item  item)  {
        MediaMetadata.Builder  metadataBuilder  =  new  MediaMetadata.Builder();
 
        metadataBuilder.putString(MediaMetadata.METADATA_KEY_MEDIA_ID,                                                              item.getId());
      metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE,                                                              item.getTitle());
              metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE,                                                              item.getSubtitle());
              metadataBuilder.putString(MediaMetadata.METADATA_KEY_DISPLAY_DESCRIPTION,                                                            item.getDescription());
              metadataBuilder.putLong(MediaMetadata.METADATA_KEY_DURATION,  mDuration);
 }  

Slide 61

Slide 61 text

PlaybackControlFragment  extends  PlaybackOverlayFragment ControlButtonPresenterSelector  presenterSelector  =  new  ControlButtonPresenterSelector();
 mPrimaryActionsAdapter  =  new  ArrayObjectAdapter(presenterSelector);
 mSecondaryActionsAdapter  =  new  ArrayObjectAdapter(presenterSelector);
 mPlaybackControlsRow  =  new  PlaybackControlsRow(mSelectedItem);   mPlaybackControlsRow.setPrimaryActionsAdapter(mPrimaryActionsAdapter);
 mPlaybackControlsRow.setSecondaryActionsAdapter(mSecondaryActionsAdapter);
 
 mPlayPauseAction  =  new  PlayPauseAction(getActivity());   mSkipPreviousAction  =  new  SkipPreviousAction(getActivity());   mPrimaryActionsAdapter.add(mPlayPauseAction);   mPrimaryActionsAdapter.add(mSkipPreviousAction);  

Slide 62

Slide 62 text

PlaybackControlFragment  extends  PlaybackOverlayFragment PlaybackControlsRowPresenter  playbackControlsRowPresenter  =  new                                                                                PlaybackControlsRowPresenter(new  DescriptionPresenter());
 playbackControlsRowPresenter.setOnActionClickedListener((action)  →  {                  if  (action.getId()  ==  mPlayPauseAction.getId())  {
                        togglePlayback(mPlayPauseAction.getIndex()  ==  PlayPauseAction.PLAY);
                }   ...   ClassPresenterSelector  presenterSelector  =  new  ClassPresenterSelector();
 presenterSelector.addClassPresenter(PlaybackControlsRow.class,  playbackControlsRowPresenter);
 presenterSelector.addClassPresenter(ListRow.class,  new  ListRowPresenter());   mRowsAdapter  =  new  ArrayObjectAdapter(presenterSelector);
 mRowsAdapter.add(mPlaybackControlsRow);   setAdapter(mRowsAdapter);   


Slide 63

Slide 63 text

MediaControllerCallback  extends  MediaController.Callback        @Override
        public  void  onPlaybackStateChanged(PlaybackState  state)  {
                if  (state.getState()  ==  STATE_PLAYING  &&  mCurrentPlaybackState  !=  STATE_PLAYING)  {
                        mCurrentPlaybackState  =  STATE_PLAYING;
                        startProgressAutomation();
                        setFadingEnabled(true);
                        mPlayPauseAction.setIndex(PAUSE);
                        mPlayPauseAction.setIcon(mPlayPauseAction.getDrawable(PAUSE));
                        notifyChanged(mPlayPauseAction);
            ...
        }
 
        @Override
        public  void  onMetadataChanged(MediaMetadata  metadata)  {
                updateItemView(metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE),
                                              metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE),
                                              metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI),
                                              metadata.getString(MediaMetadata.METADATA_KEY_DURATION)
                );
        } UI Update (fragment)

Slide 64

Slide 64 text

64 MediaSession.Callback PlaybackOverlayActivity PlaybackOverlayFragment MediaController.Callback skipToNext() VideoView playFromMediaId(item) MediaBrowserService Media Session reference

Slide 65

Slide 65 text

Similar to mobile notifications Suggest new content Resume content 65

Slide 66

Slide 66 text

RecommendationBuilder.java        Notification  notification  =  new  NotificationCompat.BigPictureStyle(                            new  NotificationCompat.Builder(mContext)                                            .setContentTitle(item.getTitle())                                            .setContentText(item.getDescription())                                            .setPriority(NotificationCompat.PRIORITY_MAX)                                            .setLocalOnly(true)                                            .setOngoing(true)                                            .setColor(getResources().getColor(R.color.selected_background))                                            .setCategory(Notification.CATEGORY_RECOMMENDATION)                                            .setLargeIcon(item.getFullImage())                                            .setSmallIcon(item.getIcon())                                            .setContentIntent(item.getIntent())                                            .setExtras(NOTIFICATION.EXTRA_BACKGROUND_IMAGE_URI,  item.getBgImage())                            .build(); defines an image to be displayed on the background when the recommendation is selected

Slide 67

Slide 67 text

Fast access to content Speech recognition 67

Slide 68

Slide 68 text

68 ListRowPresenter ArrayObjectAdapter new  ArrayObjectAdapter(new  ListRowPresenter()); CardPresenter

Slide 69

Slide 69 text

public  class  SearchFragment  extends  SearchFragment  implements  SearchResultProvider  {          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();          }   InAppSearchFragment  extends  SearchFragment searching for items should not be done on UI level

Slide 70

Slide 70 text

       @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)  {                  mDelayedLoad.setSearchQuery(words);                  mHandler.removeCallbacks(mDelayedLoad);                  mHandler.postDelayed(mDelayedLoad,  SEARCH_DELAY_MS);          }          ...                  HeaderItem  header  =  new  HeaderItem(0,  getString(R.string.search_results));          mRowsAdapter.add(new  ListRow(header,  adapter));   user pressed DPAD_CENTER real-time query update InAppSearchFragment  ...  implements  SearchResultProvider update UI with new elements

Slide 71

Slide 71 text

Pitfalls to avoid androidtv

Slide 72

Slide 72 text

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 versionName  “1.0” 72

Slide 73

Slide 73 text

Television is chewing for the eyes - Frank Lloyd Wright 73

Slide 74

Slide 74 text

https://www.youtube.com/watch?v=w7adxgUfATw https://www.youtube.com/watch?v=y3dCUPeyhag https://github.com/kingargyle/adt-leanback-support +Carlos Mota @cafonsomota [email protected] https://github.com/googlesamples/androidtv-Leanback Training https://developer.android.com/training/tv/playback/index.html GDG Porto https://play.google.com/store/apps/details?id=com.cmota.gdgportugal resources