Mastering Focus & Robot lifecycle

Understanding the focus

The robot focus is a required state for an Activity to run actions on the robot.

It is guaranteed to be owned by only one Activity at a time: this Activity is said to be the robot focus owner. This ensures that the robot focus owner is the only one to control the robot.

The robot focus is managed by a service which has the responsibility to give this focus to activities. This mechanism implies that an Activity can gain or lose the robot focus at any time.

The robot lifecycle

The QiSDK provides a robot lifecycle that allows any Activity to own the robot focus.

../_images/robot_life_cycle.png

An Activity wishing to use the QiSDK must implement the RobotLifecycleCallbacks interface:

public class MyActivity extends RobotActivity implements RobotLifecycleCallbacks

Also, the Activity must be registered to the QiSDK to receive the calls to the RobotLifecycleCallbacks methods.

It must register in the onCreate method:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // Register the RobotLifecycleCallbacks to this Activity.
    QiSDK.register(this, this);
}

And unregister in the onDestroy method:

@Override
protected void onDestroy() {
    // Unregister the RobotLifecycleCallbacks for this Activity.
    QiSDK.unregister(this, this);
    super.onDestroy();
}

Gaining the robot focus

When the Activity gains the Android focus (moves to foreground), it asks for the robot focus.

If the Activity gains the robot focus, the onRobotFocusGained callback is called:

void onRobotFocusGained(QiContext qiContext);

Warning

This callback runs on a background thread, so you cannot manipulate UI components directly in it. See Get back on UI thread to run code on the UI thread.

It provides a QiContext that allows you to:

  • create actions,
  • create resources for actions,
  • retrieve robot services.

Create an action:

// Create a Say action with a QiContext.
Say say = SayBuilder.with(qiContext)
                    .withText("Hello")
                    .build();

Create a resource:

// Create an Animation with a QiContext.
Animation animation = AnimationBuilder.with(qiContext)
                                      .withResources(R.raw.elephant_a001)
                                      .build();

Get a service:

// Get the HumanAwareness service with a QiContext.
HumanAwareness humanAwareness = qiContext.getHumanAwareness();

In this callback, you can typically run your actions:

Run an action:

// Run an action synchronously.
say.run();
// Run an action asynchronously.
say.async().run();

Losing the robot focus

When the Activity loses the robot focus, the onRobotFocusLost callback is called:

void onRobotFocusLost();

Warning

This callback runs on a background thread, so you cannot manipulate UI components directly in it. See Get back on UI thread to run code on the UI thread.

For more information on when an Activity can lose the focus, refer to Link with the Activity lifecycle.

Impact on actions

When this callback is called, this Activity cannot run actions on Pepper until it regains the robot focus:

// This will fail if robot focus is lost.
say.run();

Also, if onRobotFocusLost is called while an action is running, the action execution will stop and an exception will be raised:

// This will raise an exception.
say.run();
say.async().run().thenConsume(new Consumer<Future<Void>>() {
    @Override
    public void consume(Future<Void> future) throws Throwable {
        if (future.isSuccess()) {
            // This will not be called.
        } else if (future.hasError()) {
            // This will be called.
        }
    }
});

Because listeners can be triggered without robot focus, you should remove all listeners in the onRobotFocusLost callback:

// Remove listeners from LookAt.
if (lookAt != null) {
    lookAt.removeAllOnPolicyChangedListeners();
}

Impact on services

The robot services are not impacted when the robot focus is lost.

This means that the Futures created with services will continue their execution and the listeners associated to any service will still be triggered:

// The Future will continue its execution.
Future<List<Human>> humansAroundFuture = humanAwareness.async().getHumansAround();
// The listener will still be triggered.
humanAwareness.addOnHumansAroundChangedListener(listener);

Because listeners can be triggered without robot focus, you should remove all service listeners in the onRobotFocusLost callback:

// Remove listeners from HumanAwareness.
if (humanAwareness != null) {
    humanAwareness.removeAllOnHumansAroundChangedListeners();
}

If a service listener is not removed in the onRobotFocusLost callback, it will be triggered for all your application lifetime.

Refused robot focus

In some cases, the focus can be refused for your Activity, for example if the robot is in an unstable state. The onRobotFocusRefused callback will be called:

void onRobotFocusRefused(String reason);

The reason of the focus refusal is provided as a parameter.

Get back on UI thread

You may want to update your UI while in the robot lifecycle callbacks. For example, you could want to update a TextView.

To achieve this, you have 2 common possibilities.

Android-based solution

The Activity class provides the runOnUiThread method to execute a Runnable on the UI thread:

@Override
public void onRobotFocusGained(QiContext qiContext) {

    Say say = SayBuilder.with(qiContext)
                        .withText("Hello")
                        .build();

    // Synchronous call.
    say.run();

    // Update the TextView to notify that the Say action is done.
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            textView.setText("Done!");
        }
    });
}

QiSDK-based solution

The QiSDK provides the onUiThread static method to execute a Consumer on the UI thread:

@Override
public void onRobotFocusGained(QiContext qiContext) {

    Say say = SayBuilder.with(qiContext)
                        .withText("Hello")
                        .build();

    // Asynchronous call.
    Future<Void> sayFuture = say.async().run();

    // Update the TextView to notify that the Say action is done.
    sayFuture.andThenConsume(Qi.onUiThread(new Consumer<Void>() {
        @Override
        public void consume(Void ignore) throws Throwable {
            textView.setText("Done!");
        }
    }));
}