Android Studio logs explained through QiSDK app lifecycle

How to use logs in Android Studio to develop QiSDK application on Pepper robot
Android Studio logs explained through QiSDK app lifecycle

The aim of this tutorial is to give a quick overview of logging in the Android Studio environment. As a live example to put logs into practice, we will also focus on the application lifecycle in the QiSDK context which is based on Android lifecycle.

Introduction

Prerequisites

The first section gives a quick push up to start using the logs in your QiSDK Android app.

The second section uses logs to underline the application lifecycle process. That section is a useful complement to Mastering Focus & Robot lifecycle article.

1. How to generate log messages in Android Studio

1.1 Where are the Android logs and what do they look like?

In Android Studio when you have an application opened, to display the log messages for this app:

  1. Build and run your app on the emulator or on you real Pepper robot
  2. In the menu bar, click View > Tool Windows > Logcat or click Logcat tab in the tool window bar).
    Logcat tab

The logcat tab displays log messages based on the filters located at the top of this window.

Logcat tab in Android Studio
Logcat tab in Android Studio

The output of a log messages can look like the following lines:

Log messages
Log messages

The log message format is:

date time PID-TID/package priority/tag: message

For example, the following log message has a priority of I and a tag of MyLogTag:

04-21 18:04:35.857 2815-2849/com.softbankrobotics.tips I/MyLogTag: onRobotFocusGained method called

PID stands for process identifier and TID is thread identifier; they can be the same if there’s only one thread.

1.2 Filtering the logs

The logcat tab provides filter facilities to ease the view on log messages. Such filters are Source, Application, Log level, a string or a regular expression to match on.

Filtering logcat messages
Filtering logcat messages

1.3 Set the log level

You can set a log level to messages. It will be useful to control how many messages appear in logcat and give information on the severity of what info you intend to share. You can display all messages, or just the messages indicating the most severe conditions.

Logcat continues to collect all messages regardless of the log level setting in the filter. The setting determines what logcat displays.

Here are the possible Log levels available

  • ERROR / Log.e display error messages.
  • WARN / Log.w display warnings.
  • INFO / Log.i display information messages/ expected log messages.
  • DEBUG / Log.d display debug messages.
  • VERBOSE / Log.v display all log messages. (least used as it records more info than usual logging)

1.4 Log methods

Some of the log messages displayed above were generated with the following line of code :

   Log.i("MyLogTag", "onRobotFocusGained method called")
   Log.d("MyLogTag", "# fake run on say action")
   Log.e("MyLogTag",  "You generated an error")
   Log.w("MyLogTag", "Be careful !")

Log methods use 2 arguments. There is a prototype with 3 we don’t deal with it here.

Log.d(String tag, String msg)

  1. A String called TAG. It can be used to filter logs to limit the log output to specific data. A TAG can be any string that you find helpful (eg: name of the class).
  2. Second argument is msg. It is the main content of the log message.

Declare a TAG constant

It is useful to declare a TAG constant in your class and use it as the first argument of the Log method. Often the class name is used as a log tag.

In Kotlin

private const val TAG = "MyLogTag"
...
Log.i(TAG, "onRobotFocusGained method called")

In JAVA

private static final String TAG = "MyLogTag";
...
Log.i(TAG, "onRobotFocusGained method called");

1.5 Useful options

Some useful helpers are located on the Logcat window sidebar.

Clear logcat

  

Clear Logcat

will clear the visible log.

Scroll to the end

  

Scroll to the end

will let you see the latest log messages.

Up the stack trace

  

Up the stack trace

for navigating up the stack traces.

Down the stack trace

  

Down the stack trace

for navigating down the stack traces.

Soft wraps

  

Soft wraps

will enable line wrapping.

Restart

  

Restart

will clear the log and restart it.

1.6 To sum up log messages in Android Studio

Here was a quick introduction on how to use logs in Android Studio. Android official documentation will guide you in more details. Consider the following documentation pages.

Now let’s put this in practice to underline the application lifecycle in the next section.

2. Highlight on the Android and QiSDK application lifecycle using the logs

2.1 What is the Android application lifecycle

Here is a quick reminder below about Android lifecycle architecture.

When you launch an application on an Android device several callback functions are called one after the other depending on what is the status of the app. In the Getting started you have used these two methods:

  • onCreate()
  • onDestroy()

You can also add the other callbacks of the android app lifecycle which are also usable in your Pepper apps.

The other Android lifecycle callbacks are :

  • onStart()
  • onRestart()
  • onResume()
  • onPause()
  • onStop()

As a reminder the android callback are called in these situations :

  1. onCreate() : when activity is created. Objects and views are initialized here.
  2. onStart() / onRestart() : when activity is partially visible.
  3. onResume() : when activity is fully visible.
  4. onPause() : when activity is partially hidden.
  5. onStop() : when activity is completely hidden.
  6. onDestroy() : when activity is destroyed (via the app or by the android system)

This is what is referred to as the Android lifecycle of an application and clearly summarized in the following schema:

Android lifecycle schema
Android lifecycle schema

2.2 What QiSDK adds to the app lifecycle

Now let’s present three more callbacks specific to the QiSDK environment. If you have already gone through the Getting started tutorial you have already heard about them.

The callbacks are:

  • onRobotFocusGained(),
  • onRobotFocusLost()
  • onRobotFocusRefused()

As a reminder the QiSDK callbacks are called in these situations :

  1. onRobotFocusGained() : when activity becomes fully visible, ie when onResume() is called
  2. onRobotFocusLost(): when activity is partially hidden, ie when onPause() is called
  3. onRobotFocusRefused(): when the focus could not be obtained due to situations such as Pepper is in ‘rest mode’, a ‘safety’ is activated or he did not fish to boot.

Tips on app Android for Pepper QiSDK

  • You can register to QiSDK callbacks QiSDK.register(myActivity, myObject) wherever in the code from the UI Thread. Usually you connect to in onCreate(), onStart() or onResume() and unregister in symmetric callbacks.
  • An activity gains robotic focus when activity is fully visible and the connection to the robot is successful (it corresponds to the time when onResume() method is called). It loses focus when activity is partially hidden (when onPause() method is called).

The three onRobot methods take part of the Android lifecycle ballet as you can figure out in the following schema:

QiSDK onRobot callback methods schema
QiSDK onRobot callback methods schema

2.3 How to highlight the callback calls

Let’s use logs in each of the callbacks presented above and take it to action playing with the app, in order to show what logs are generated at runtime.

Complete you template application with al the lifecycle callbacks and add Log.i calls as follow:

Kotlin code

package com.softbankrobotics.tips

import android.os.Bundle
import android.util.Log
import com.aldebaran.qi.sdk.QiContext
import com.aldebaran.qi.sdk.QiSDK
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks
import com.aldebaran.qi.sdk.design.activity.RobotActivity

private const val TAG = "MyLogTag"

class MainActivity : RobotActivity(), RobotLifecycleCallbacks {

   override fun onCreate(savedInstanceState: Bundle?) {
       Log.i( TAG, "onCreate method called")
       super.onCreate(savedInstanceState)
       // Register the RobotLifecycleCallbacks for this Activity.
       QiSDK.register(this, this)
       setContentView(R.layout.activity_main)
   }

   override fun onDestroy() {
      Log.i(TAG, "onDestroy method called")
      // Unregister the RobotLifecycleCallbacks for this Activity.
      QiSDK.unregister(this, this)
      super.onDestroy()
   }

   override fun onRobotFocusGained(qiContext: QiContext?) {
       // The robot focus is gained.
       Log.i( TAG, "onRobotFocusGained method called")
   }

   override fun onRobotFocusRefused(reason: String?) {
       // The robot focus is refused.
       Log.e( TAG, "onRobotFocusRefused method called. Error message is: $reason")
   }

   override fun onRobotFocusLost() {
       // The robot focus is lost.
       Log.i(TAG, "onRobotFocusLost method called")
   }

   override fun onStart() {
       Log.i( TAG, "onStart method called")
       super.onStart()
   }

   override fun onRestart() {
       Log.i( TAG, "onRestart method called")
       super.onRestart()
   }

   override fun onResume() {
       Log.i( TAG, "onResume method called")
       super.onResume()
   }
}

JAVA code


package com.softbankrobotics.tips;

import android.os.Bundle;
import android.util.Log;
import com.aldebaran.qi.sdk.QiContext;
import com.aldebaran.qi.sdk.QiSDK;
import com.aldebaran.qi.sdk.RobotLifecycleCallbacks;
import com.aldebaran.qi.sdk.design.activity.RobotActivity;

public class MainActivity extends RobotActivity implements RobotLifecycleCallbacks {

   private static final String TAG = "MyLogTag";

   @Override
   protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       Log.i(TAG, "onCreate method called");
       // Register the RobotLifecycleCallbacks to this Activity.
       QiSDK.register(this, this);
       setContentView(R.layout.activity_main);
   }

   @Override
   protected void onDestroy() {
       Log.i(TAG, "onDestroy method called");
       // Unregister the RobotLifecycleCallbacks for this Activity.
       QiSDK.unregister(this, this);
       super.onDestroy();
   }

   @Override
   public void onRobotFocusGained(QiContext qiContext) {
       // The robot focus is gained.
       Log.i(TAG, "onRobotFocusGained method called");
   }

   @Override
   public void onRobotFocusLost() {
       // The robot focus is lost.
       Log.i(TAG, "onRobotFocusLost method called");
   }

   @Override
   public void onRobotFocusRefused(String reason) {
       // The robot focus is refused.
      Log.e(TAG, "onRobotFocusRefused method called. Error message is: " + reason);
   }

   @Override
   protected void onStart() {
       super.onStart();
       Log.i(TAG, "onStart method called");
   }

   @Override
   protected void onResume() {
       super.onResume();
       Log.i(TAG, "onResume method called");
   }

   @Override
   protected void onRestart() {
       super.onRestart();
       Log.i(TAG, "onRestart method called");
   }

   @Override
   protected void onPause() {
       super.onPause();
       Log.i(TAG, "onPause method called");
   }

   @Override
   protected void onStop() {
       super.onStop();
       Log.i(TAG, "onStop method called");
   }
}

2.4 Run your activity and view log messages

Now put the code above into action running the activity on the emulator or on your live robot.

Look at the logcat tab that should display the log messages your code generates. Now swipe the activity out, reactivate your app, kill it, etc. and check at the log message list using the features presented at top of the article.

The following video shows the log messages displayed depending on action on the emulated tablet.


Using logs demo; mp4, 2'25''

You got it all ! Congrats !

Code well, log well... and don’t forget to add comments to your code ;-)

Green Guy with glasses
Louis-Gabriel POUILLOT
Senior Software Engineer