Activity detector

Publicado el 19/10/2016 por by Abel Toledano, Senior Software Engineer

Sometimes, when you want to do some action in your page using JavaScript it’s very convenient to know whether the user is actually paying attention to your page at that moment, because, as you probably know, the JS in your page can be executing even when it is in background. For example, the user can be reading a different browser tab, or even using a different application and the browser doesn’t have the focus.

In Tuenti we have had to deal with this kind of problems in different situations. One clear example is the Tuenti chat: we want to notify the user about new messages playing a notification sound, but we don’t want to play it when the user is already paying attention to the conversation, we only want to play the notification sound when the page is in background to take the user’s attention.

To achieve this we listen to different browser events to determine whether the user is actually using Tuenti at that moment or not. These are some of the events we listen to:

focus and blur events in window:

When a user enters to your page, the browser fires a focus event in the window object. In the same way, when the user changes to a different tab or application, the browser fires a blur event.

Taking this into account, it’s pretty simple to determine whether the user is in your tab:

var inTab = true;

window.addEventListener('focus', function () {
  inTab = true;
});

window.addEventListener('blur', function () {
  inTab = false;
});

Pretty simple, isn’t it?

Well… it depends on what you really need. With this simple code you can know whether the user is in your tab, but what happens if the user is in your tab but he has left the computer and is in the bathroom?

So we don’t only need to know if the user is in our tab but also if he is using it. To solve this problem we could connect the laptop webcam and spy the user, but probably our users wouldn’t like that. So we can use a simpler (and more privacy respectful) solution:

Mouse and keyboard events

We can listen to DOM user interaction events to know if the user is actually using the page, and when we have not heard any event for a given time span (for example 30 seconds) we can assume the user is not active. Some events we can listen to are: click, mousemove, keydown, etc (for desktop devices), or touchstart, touchmove, etc. (for mobile devices).

So we can attach some event listeners to the document and toggle the inTab variable in the previous example to true. But, when we change it to false? when it has elapsed 30 seconds since the last event. This behavior can be implemented with a setTimeout, but we also need to reset that timeout (with a clearTimeout) when we hear a new event.

We can express this solution with a simple state machine:

state machine

Ok, beautiful, let’s implement it. You don’t need to do it! because we have already done it and opensourced a library that you can use: activity-detector:

How to use


First, install it.

We distribute the lib in npm, so you can install it with a simple command:

$ npm install --save activity-detector

Let’s see a basic example

// 1. import the lib:
import createActivityDetector from 'activity-detector';

// 2. create an activity-detector instance:
const activityDetector = createActivityDetector();

// 3. subscribe to events
activityDetector.on('idle', () => {
  console.log('The user is not interacting with the page');
});

activityDetector.on('active', () => {
  console.log('The user is using the page');
});

How this work? the library implements the algorithm we have presented in the previous state machine diagram. When you create an instance of activity-detector you can subscribe to the events active and idle. Those events will be triggered when the user becomes ACTIVE or INACTIVE respectively.

And that’s all. Now you can detect when your user is using your page or not.

Ok, but I don’t use npm neither ES6. Can I use activity-detector lib?

Of course, we have a production ready build (UMD format) that you can simply include in your page with a simple <script> tag, or require it with any module loader (like require.js). You can find it here.

For example:



  
    
    test activity detector
    
  
  
    

Advanced options

activity-detector supports different config options to customize its behavior if you have different needs. For example you can decide which user events should be considered, or defer the activity-detector initialization. For example:

const activityDetector = createActivityDetector({
    timeToIdle: 10000, // wait 10s of inactivity to consider the user is idle
});

You can see the different options reading the docs in the github repo:

https://github.com/tuenti/activity-detector/

If you like it and think it’s useful don’t forget to star it. And if you miss some feature or find a bug please open an issue or, even better, send us a pull request!

Tuenti for Android TV

Publicado el 04/2/2015 por Pedro Vicente Gómez Sánchez, Senior Software Engineer

Less than a year ago, in 2014, Google announced the Google IO event, a really important milestone for the Android Developers Community. Android TV was introduced to the world, a new adaptation of the Android OS based on Android Lollipop for televisions which some manufacturers like Samsung or LG are already using as the OS for their televisions, and which Google is already using as the OS inside its new Nexus Player.


The Tuenti team tries to be always updated and continues learning about new frameworks or technologies. This is the reason why we are going to review some of the most important features of Android TV and provide you with some examples about how to create an awesome application using Android TV features. To provide the best examples we can, we have published a public repository on Github you can fork and use to review some of the concepts explained in this blog post. Every Github star is welcome. In this repository you are going to find all the code needed to create a basic Android TV application. Take into account that all the information shown in this repository is mocked, we just wanted to show how to work over the Android TV UI layer and not how to implement the full application. Here you have a video with the final result:

The first approach to this project was not related to the code, but to the user experience and the user interface. To help me with this, Luis Javier Á., Emanuela M., Juan Manuel J. and Carmen L. came to rescue me and designed the product you can see in the previous video. The key of this development was the “focus” concept. The whole user experience and the user interface are related to this concept because the Android TV devices don’t support touchscreens. Trying to avoid soft keyboard usage and how to use the television background to provide more context to the user without distracting them were also two interesting challenges.


You can review how these ideas are actually present during the login process, where the account used to log in is shown applying a scale up effect when the view gets the focus. The “enter password” user interface has been implemented to use the Nexus Player remote instead of the operative system soft keyboard, in order to improve how the user interacts with the application and to avoid the usage of a small soft keyboard.


Regarding the application development, in this blog post we will review some of the most important details for the Android TV applications development:

  • Configure your project.
  • Create a browse view.
  • Create a detail view.
  • Create a search view.
  • Without Leanback library.

Configure your project:

To be able to create an application for Android TV you’ll have to provide one important dependency, Leanback Android TV library, which depends on V7 App Compat library and V7 RecyclerView library. These dependencies have to be added to your build.gradle file or your pom.xml, depending on the build tool used in your project. Here you have a sample to show how to add these dependencies to a project using Gradle:

dependencies{
    compile 'com.android.support:leanback-v17:21.0.3'
    compile 'com.android.support:appcompat-v7:21.0.3'
    compile 'com.android.support:recyclerview-v7:21.0.3'
}

Once you have your dependencies added to the project, you’ll have to configure one of your activities as the Activity used by the operative system when your application starts, that is to say your launch activity. Using this intent filter you application will be ready to be launched. Here you have a sample that shows how to configure an activity to make it work as the launcher activity for an Android TV project:


 
      
        

        
      
    

Create a browse view:

Browse views are one of the most important views you can use to show content in your application. BrowseFragment is a fragment for creating Leanback browse screens. It is composed of a RowsFragment and a HeadersFragment. A BrowseFragment renders the elements of its object adapter as a set of rows in a vertical list. The elements in this adapter must be subclasses of Row.


To use this Fragment inside your application you can create your own Fragment extending from Leanback BrowseFragment or add an instance of BrowseFragment to your Activity layout and get a reference using Android Fragment Manager. We have created our own Fragment, extending from Leanback BrowseFragment and configuring it to show information related to contacts inside the user interface. To do this, we have used some important methods like: setBrandColor, setSearchAffordanceColor or setBadgeDrawable.

  @Override public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setBrandColor(getResources().getColor(R.color.primary_color));
    setSearchAffordanceColor(getResources().getColor(R.color.primary_color));
    setBadgeDrawable(getResources().getDrawable(R.drawable.icn_wink));
  }

To show information inside the BrowseFragment you’ll have to use an ArrayObjectAdapter. This Android class is thought to work like a bidimensional matrix where every row is another ArrayObjectAdapter and HeaderItem objects are used as the title for every row. The information shown is rendered using a Presenter instance. In this project you can see different Presenter implementations like ImagePresenter -created to show a simple image without any other visual effect- or IconPresenter -created to show preferences inside MainFragment-. ArrayObjectAdapter class is provided by Leanback Android library.

   @Override public void showMainInformation(List favorites, List conversations, List contacts, List mediaElements, List preferences) {
    ArrayObjectAdapter rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
    CardPresenter bigCardPresenter = new CardPresenter(CARD_WIDTH_IN_DP, CARD_HEIGHT_IN_DP);
    CardPresenter smallCarPresenter = new CardPresenter();

    addCardInfoElementsToRowsAdapter(R.string.favorites_item_title, favorites, rowsAdapter,
        smallCarPresenter, FAVORITES_ROW);
    addCardInfoElementsToRowsAdapter(R.string.recent_conversation_item_title, conversations,
        rowsAdapter, bigCardPresenter, CONVERSATIONS_ROW);
    addCardInfoElementsToRowsAdapter(R.string.contacts_item_title, contacts, rowsAdapter,
        smallCarPresenter, CONTACTS_ROW);
    addImageInfoElementsToRowAdapter(R.string.media_elements_item_title, mediaElements, rowsAdapter,
        new ImagePresenter(), MEDIA_ROW);
    addIconInfoElementsToRowAdapter(getResources().getString(R.string.preferences), preferences,
        rowsAdapter, new IconPresenter(), PREFERENCES_ROW);

    setAdapter(rowsAdapter);
  }
   private void addIconInfoElementsToRowAdapter(String title, List preferences,ArrayObjectAdapter rowsAdapter, Presenter presenter, int id) {
    ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(presenter);
    for (IconInfo iconInfo : preferences) {
      listRowAdapter.add(iconInfo);
    }
    rowsAdapter.add(new ListRow(new HeaderItem(id, title, ""), listRowAdapter));
  }

Configuring events listeners is also important to be able to react to user movements. For this sample we have configured onItemViewClickedListener and onItemViewSelectedListener.

 private void configureListeners() {

    setOnItemViewSelectedListener(new OnItemViewSelectedListener() {
      @Override public void onItemSelected(Presenter.ViewHolder viewHolder, Object item, RowPresenter.ViewHolder viewHolder1, Row row) {
        if (row.getId() < MEDIA_ROW) {
          presenter.onCardInfoSelected((CardInfo) item);
        } else if (row.getId() == MEDIA_ROW) {
          presenter.onImageInfoSelected((ImageInfo) item);
        } else if (row.getId() == PREFERENCES_ROW) {
          presenter.onPreferencesSelected();
        }
      }
    });

    setOnItemViewClickedListener(new OnItemViewClickedListener() {
      @Override public void onItemClicked(Presenter.ViewHolder viewHolder, Object item,
          RowPresenter.ViewHolder viewHolder1, Row row) {
        if (row.getId() == PREFERENCES_ROW) {
          int id = ((IconInfo) item).getIconId();
          switch (id) {
            case R.drawable.icn_settings_log_out:
              presenter.logout();
              break;
            default:
          }
        } else if (row.getId() == MEDIA_ROW) {
          presenter.onImageInfoClicked((ImageInfo) item);
        } else if (row.getId() < MEDIA_ROW) {
          presenter.onCardInfoClicked((CardInfo) item);
        }
      }
    });
  }

Create a detail view:

The detailed view has been one of the key elements of the user interface for this project. We need to show detailed information about user’s contacts and some actions related to the content.


To be able to show detailed information we have created a custom Fragment extending from Leanback DetailsFragment with the same strategy we have used to create our BrowseFragment extension. To show information inside this fragment you can also use an ArrayObjectAdapter and different presenters as we have already done with the previous fragment. However, to show the detailed view you have to create a DetailsOverviewRow and a ClassPresenterSelector used to fill the ArrayObjectAdapter used inside the DetailsFragment. You can add more information to the ArrayObjectAdapter used and show a row full of data like the one shown in the previous sample.

private void showDetailInformation(Bitmap bitmap, CardInfo cardInfo){
        DetailsOverviewRow detailRow = configureDetailsOverviewRow(cardInfo, bitmap);
        ClassPresenterSelector presenterSelector = new ClassPresenterSelector();
        DetailsOverviewRowPresenter dorPresenter =
            new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());
        dorPresenter.setBackgroundColor(getResources().getColor(R.color.primary_color));
        dorPresenter.setStyleLarge(false);
        presenterSelector.addClassPresenter(DetailsOverviewRow.class, dorPresenter);
        presenterSelector.addClassPresenter(ListRow.class, new ListRowPresenter());
        adapter = new ArrayObjectAdapter(presenterSelector);
        adapter.add(detailRow);
        setAdapter(adapter);
  }

To show actions inside the detail view you can add Action objects to your DetailsOverviewRow.

  private DetailsOverviewRow configureDetailsOverviewRow(CardInfo cardInfo, Bitmap bitmap) {
    final DetailsOverviewRow row = new DetailsOverviewRow(cardInfo);
    row.setImageBitmap(getActivity(), bitmap);
    row.addAction(new Action(VD_CALL_ACTION_ID, getString(R.string.vd_call_action_title)));
    row.addAction(new Action(CALL_ACTION_ID, getString(R.string.call_action_title)));
    row.addAction(new Action(CHAT_ACTION_ID, getString(R.string.chat_action_title)));
    row.addAction(new Action(EDIT_ACTION_ID, getString(R.string.edit_action_title)));
    return row;
  }

Another important element to use in your detail views is the background. Remember that creating your custom Picasso Target and using Picasso as the main image management library you can perform background changes easily.

 private void configureBackground() {
    metrics = new DisplayMetrics();
    getActivity().getWindowManager().getDefaultDisplay().getMetrics(metrics);
    BackgroundManager backgroundManager = BackgroundManager.getInstance(getActivity());
    backgroundManager.attach(getActivity().getWindow());
    backgroundTarget = new PicassoBackgroundManagerTarget(backgroundManager);
  }
  
  @Override public void showBackground(String backgroundUrl) {
    Picasso.with(getActivity())
        .load(backgroundUrl)
        .resize(metrics.widthPixels, metrics.heightPixels)
        .error(R.drawable.fragment_default_background)
        .centerCrop()
        .into(backgroundTarget);
  }

Create a search view:

In order to show a user interface that performs searches in your application using a widget based on Leanback library, you’ll have to follow an approach similar to the previous Fragment usage. Create your own Fragment and extend it from Leanback SearchFragment.


To be able to react when the user updates the search edit text you’ll have to provide a SearchResultProvider implementation and implement two methods: onQueryTextChange and onQueryTextSubmit. Implementing this method you’ll be able to perform your custom searches using a String search term.

  @Override public boolean onQueryTextChange(String newQuery) {
    Log.d(TAG, String.format("Search Query Text Change %s", newQuery));
    queryByWords(newQuery);
    return true;
  }

  @Override public boolean onQueryTextSubmit(String query) {
    Log.d(TAG, String.format("Search Query Text Submit %s", query));
    queryByWords(query);
    return true;
  }

As in the previous sample, how we can show information inside this Fragment is based on the usage of one ArrayObjectAdapter object, HeaderItems and Presenter instances.

  @Override public void showAllContacts(List contacts) {
    ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(new CardPresenter());
    for (Contact contact : contacts) {
      arrayObjectAdapter.add(contact);
    }
    HeaderItem headerItem = new HeaderItem(getString(R.string.contacts_item_title), "");
    rowsAdapter.add(new ListRow(headerItem, arrayObjectAdapter));
  }

  @Override public void showSearchResultContacts(String query, List contacts) {
    ArrayObjectAdapter arrayObjectAdapter = new ArrayObjectAdapter(new CardPresenter());
    for (Contact contact : contacts) {
      arrayObjectAdapter.add(contact);
    }
    String resultSearchTitle = getString(R.string.search_result, query);
    HeaderItem headerItem = new HeaderItem(resultSearchTitle, "");
    rowsAdapter.add(new ListRow(headerItem, arrayObjectAdapter));
  }

If your user interface has to react to user clicks you’ll have to provide a OnItemViewClickedListener implementation to the DetailsFragment.

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setSearchResultProvider(this);
    rowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
    setOnItemViewClickedListener(new OnItemViewClickedListener() {
      @Override public void onItemClicked(Presenter.ViewHolder viewHolder, Object item, RowPresenter.ViewHolder presenterViewHolder, Row row){
        presenter.onContactClicked((Contact) item);
      }
    });
  }

Without Leanback library:

If your user interface has to be built on top of non so common user interface widgets you’ll have to review some important concepts:

  • Focus usage is going to be fundamental, learn how to use nextFocusX attributes in your layout widgets and how to use <requestFocus/> label.

 

    

  


  • State List Animators are going to help you to highlight some of your widgets when a widget is selected.


  
    
      
      
      
      
    
  
...

  • getCurrentFocus()” method will give you the UI widget selected by the user and is going to be really useful if you have to change your UI programmatically. OnFocusChangedListener is also really interesting to provide dynamic UI changes.
  @Override public void hidePreviousPasswordElements() {
    View currentViewWithFocus = getCurrentFocus();
    ViewGroup parent = (ViewGroup) currentViewWithFocus.getParent();
    for (int i = 0; i < parent.getChildCount(); i++) {
      View ib_element_item = parent.getChildAt(i);
      if (currentViewWithFocus.equals(ib_element_item)) {
        ImageButton previousFocusedElement = (ImageButton) parent.getChildAt(i - 1);
        updatePasswordElementWithAsterisk(previousFocusedElement, 0);
      }
    }
  }

    @OnFocusChange(R.id.iv_app_logo) void onFocusChanged(boolean focused) {
    if (!focused) {
      iv_app_logo.setFocusable(false);
    }
  }
  • Displaying recommended content using the Android Notifications API will help you to show important content to your users. It’s as easy as displaying an old Android notification.
    Notification notification = new NotificationCompat.BigPictureStyle(
        new NotificationCompat.Builder(context).setContentTitle(title)
            .setContentText(description)
            .setPriority(priority)
            .setLocalOnly(true)
            .setOngoing(true)
            .setColor(context.getResources().getColor(R.color.primary_color))
            .setCategory(Notification.CATEGORY_RECOMMENDATION)
            .setLargeIcon(image)
            .setSmallIcon(smallIcon)
            .setContentIntent(pendingIntent)
            .setExtras(extras)).build();

    notificationManager.notify(id, notification);

Conclusions:

Create a sample application based on the Tuenti product has been a really interesting challenge, not just from the coding point of view, but an interesting challenge from the UX, UI and Product point of view. User interaction with this new set of devices is really different, and how you have to base the UI state in the application focus is a different way to think in an Android device. The usage of the Activity/Fragment background to provide more context to the user and use scale up/down animations to guide the user navigation through the application was really funny. From the development side, to code for Android TV is really easy if your UI has to use Leanback base components, but if you want to provide a different user experience based on a different user interface you’ll have to do your best and start using state list animators and other new features from API 21. Review also some base components of Android TV Leanback library could be interesting to provide a customized UX but based on the original components. Review RowsFragment and HeadersFragment if needed. Explaining in this blog post how to use the full Leanback library to build applications for Android TV would be impossible, so I recommend you to take a look at the Github repository we have prepared for you and to review the usage of some non familiar elements like ArrayObjectAdapter, HeaderItem objects or other minor details like how to configure the application icon for Android TV.

RoboRouter, a little library to clean your main activity

Publicado el 07/11/2014 por Rayco Araña, Software Engineer

RoboRouter is a very small and simple library with the purpose of simplify the start-up of our apps. It works by activating/deactivating the main, login and walkthrough activities of your app. This way you don’t have to execute code to verify if you have to show the walkthrough or the login screen every time the user starts the application.

RoboRouter only needs a few lines of code to get it working. The first one, at the onCreate() method of your Application, initializes the library. Then you only have to let the “next” button at the Walkthrough/Login screen call RoboRouter to move to the next screen. The library does all the necessary work to disable the walkthrough/login/main activity component, enable the one that has to be shown and start it.

SmsRadar, First Open Source Android Library released by Tuenti

Publicado el 02/6/2014 por Pedro Gómez, Mobile Apps Engineer

SmsRadar is the first open source library released by our Android team. This library has been implemented to listen to every incoming and outgoing SMS that the user’s device receives or sends. This Android library works for Android 2.X or higher versions.

SmsRadar has been implemented because intercepting incoming SMS is not a trivial task, if an Android developer wants to intercept an outgoing SMS he can use BroadcastReceivers because Android SDK allows you
to listen to SMS intent filter. But, what about incoming SMS? You can’t use BroadcastReceivers to implement this because there is no intent filter to listen to incoming SMS. Another option is to configure your application to work as an SMS native application, but this SDK feature is not available for all Android versions.

This Android library implementation is based on an Android SDK element called ContentObserver. To be able to listen to a native SMS database we have registered a ContentObserver implementation called SmsObserver to the native SMS ContentProvider. The following class diagram represents the main classes involved in this library.


The visible part of this library -from the library user's point of view- is:

To use SmsRadar developers have to register SmsRadarService into their AndroidManifest and start/stop SmsRadar library using two main methods available in SmsRadar class: “initializeSmsRadarService” and “stopSmsRadarService”.


SmsRadar is just the first Android library released by Tuenti. We will continue working to contribute to Android Open Source Community.

Tuenti Hosts the 6th PHPMad Meetup about HHVM

Publicado el 29/5/2014 por Eng. Outreach Committee

As every month, we hosted the 6th PHPMad meeting in the kitchen of our central Madrid office. Maximo Cuadros talked about The Hitchhiker's Guide to the HHVM. HHVM is an open-source virtual machine designed for executing programs written in Hackand PHP. He brought us an introduction about the stuff we will see in the deSymfonyDay.

In addition to PHPMad, we are open to hosting other user group meetings and talks so, if you’re interested in organising a tech or design-related event, get in touch!

Pages

Siguenos