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 QiSDK provides a robot lifecycle that allows any Activity
to own the
robot focus.
An object wishing to use the QiSDK must implement the RobotLifecycleCallbacks
interface.
For example, the Activity
itself can implement it:
class MyActivity : RobotActivity(), RobotLifecycleCallbacks {
public class MyActivity extends RobotActivity implements RobotLifecycleCallbacks
Also, this object must be registered to the Activity
to receive the calls
to the RobotLifecycleCallbacks
methods.
It may register in the onCreate
method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Register the RobotLifecycleCallbacks to this Activity.
QiSDK.register(this, this)
}
@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 fun onDestroy() {
// Unregister the RobotLifecycleCallbacks for this Activity.
QiSDK.unregister(this, this)
super.onDestroy()
}
@Override
protected void onDestroy() {
// Unregister the RobotLifecycleCallbacks for this Activity.
QiSDK.unregister(this, this);
super.onDestroy();
}
Important
For simplicity sake, these tutorials recommend registering and
unregistering in onCreate
and onDestroy
of an Activity
object.
More complex scenarios may benefit from or require other strategies.
The developer has fine-grained control of when they wish to actually receive the callbacks, by registering and unregistering when needed.
And it is not necessary to implement these callbacks into the
Android Activity
object, they can be in any object.
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 for each RobotLifecycleCallbacks
registered to this Activity
:
fun onRobotFocusGained(qiContext: QiContext)
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 an action:
// Create a Say action with a QiContext.
val say: Say = SayBuilder.with(qiContext)
.withText("Hello")
.build()
// Create a Say action with a QiContext.
Say say = SayBuilder.with(qiContext)
.withText("Hello")
.build();
Create a resource:
// Create an Animation with a QiContext.
val animation: Animation = AnimationBuilder.with(qiContext)
.withResources(R.raw.elephant_a001)
.build()
// 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
val humanAwareness: HumanAwareness = qiContext.humanAwareness
// 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 synchronously.
say.run();
// Run an action asynchronously.
say.async().run()
// Run an action asynchronously.
say.async().run();
When the Activity
loses the robot focus, the onRobotFocusLost
callback is
called for each RobotLifecycleCallbacks
registered to this Activity
:
fun onRobotFocusLost()
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, and for best practices,
refer to Robot lifecycle and Activity lifecycle and Disabled Robot State case.
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()
// 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()
// This will raise an exception.
say.run();
say.async().run().thenConsume { future ->
if (future.isSuccess) {
// This will not be called.
} else if (future.hasError()) {
// This will be called.
}
}
say.async().run().thenConsume(future -> {
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.
lookAt?.removeAllOnPolicyChangedListeners()
// Remove listeners from LookAt.
if (lookAt != null) {
lookAt.removeAllOnPolicyChangedListeners();
}
The robot services are not impacted when the robot focus is lost.
This means that the Future
s created with services will continue their
execution and the listeners associated to any service will still be triggered:
// The Future will continue its execution.
val humansAroundFuture: Future<List<Human>> = humanAwareness.async().humansAround
// The Future will continue its execution.
Future<List<Human>> humansAroundFuture = humanAwareness.async().getHumansAround();
// The listener will still be triggered.
humanAwareness.addOnHumansAroundChangedListener{ listener }
// 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.
humanAwareness?.removeAllOnHumansAroundChangedListeners()
// 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.
In some cases, the focus can be refused for your Activity
, for example if
the robot is in Sleep Mode or in Disabled State.
The onRobotFocusRefused
callback will be called for each RobotLifecycleCallbacks
registered to this Activity
:
fun onRobotFocusRefused(reason: String)
void onRobotFocusRefused(String reason);
The reason of the focus refusal is provided as a parameter.
For more details on best practices, refer to Robot lifecycle and Activity lifecycle and Disabled Robot State case.
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.
The Activity
class provides the runOnUiThread
method to execute a
Runnable
on the UI thread:
override fun onRobotFocusGained(qiContext: QiContext) {
val say: Say = SayBuilder.with(qiContext)
.withText("Hello")
.build()
// Synchronous call.
say.run()
// Start the basic emotion observation.
runOnUiThread { textView.text = "Done!"}
}
@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(() -> textView.setText("Done!"));
}
The QiSDK provides the onUiThread
static method to execute a Consumer
on the UI thread:
override fun onRobotFocusGained(qiContext: QiContext) {
val say: Say = SayBuilder.with(qiContext)
.withText("Hello")
.build()
// Asynchronous call.
val animateFuture = say.async().run()
// Update the TextView to notify that the Say action is done.
sayFuture.andThenConsume(Qi.onUiThread(Consumer {
textView.text = "Done!"
}))
}
@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((Consumer<Void>) ignore -> {
textView.setText("Done!");
}));
}
The robot lifecycle is not entirely linked to the Activity
lifecycle.
However, if you consider two Activity
instances A and B, the
Activity
A will lose the robot focus if:
Activity
changes from A to B,Important
In any case, an Activity
that owns the robot focus and becomes
not visible (even partially) on the UI will lose the robot focus.
There is no guarantee that one of the callbacks onRobotFocusGained or
onRobotFocusRefused will be called. In some corner cases such as communication
difficulties between the robot and tablet, the RobotLifecycleCallbacks
may never be notified of the focus gain or the focus denial. The application
should implement its own time-out in order to handle this contingency and avoid
waiting indefinitely.
There is no way to explicitly (re)request the robot focus. When the Android
Activity
focus changes, the right to robot focus ownership implicitly moves
with it. Registering to RobotLifecycleCallbacks
tells your object if the
activity passed to it has the robot focus or not.
RobotLifecycleCallbacks will only be called once for each object’s registration.
For example, after receiving onRobotFocusRefused
or onRobotFocusLost
, it
is not possible to receive onRobotFocusGained
again, as long as the activity
is still entirely visible.
To receive further callbacks, it is necessary to register a new object,
or unregister and re-register the current object.
Be diligent of Activity
and robot focus lifecycles when using the startActivity
API.
Important
Calling a startActivity
means onRobotFocusLost
for the current activity.
After calling startActivity
, a new Activity
will be focused and then
onRobotFocusLost
will be called for the previous activity. So be sure that your
onRobotFocusLost
callback will not interfere with the new Activity
.
Here are some pitfalls to avoid:
Be careful when calling startActivity
in RobotLifecycleCallbacks
such as
onRobotFocusLost
. This can lead to unexpected application states. For example
if onRobotFocusLost
had been called as a result of a different Activity
focus change, you risk unintentionally taking the Android Activity
focus from it.
It is usually recommended to not call startActivity
from an activity not visible
on the screen. Most of the time, it leads to unexpected results.
For example, avoid calling startActivity
from a background
service and make sure to cancel any asynchronous task that lead to an activity
launch when the activity that runs it is closed or hidden.
Likewise, it is not recommended to call startActivity
from Android focus lifecycle callbacks
such as onCreate
, onStart
, onResume
, onPause
, onStop
and onDestroy
.
Sleep Mode specific case:
Important
When Pepper enters Sleep Mode, a black Android Activity
is focused. This may appear as unexpected from the perspective
of your application.
Your activity’s onPause
method will first be called in this case.
Then, after the black Activity
appears, code registered in
your Activity
to RobotLifecycleCallbacks
will receive the
onRobotFocusLost
callback.
While Pepper is still in Sleep Mode, requests for robot focus
will be answered with the onRobotFocusRefused
callback.
Pepper’s Disabled State is entered by double-clicking the chest button. Then Pepper enters a rest position with all motors off.
In Disabled State it is not possible for the QiSDK to control Pepper. It cannot receive the robot focus.
Important
Disabled State is not intended to be used in end-user applications
as it interferes with the
expected flow of Activity
and robot focus lifecycles.
As Disabled State can only be entered and exited manually by a knowledgeable user, it is usually not necessary to automatically detect and handle this case from your application.
As noted in Performance and limitations only the newly
displayed Activity
will request the robot focus.
So after exiting Disabled state, it is recommended for the person
who disabled the robot to simply restart their application, or
change the displayed activity within it. It is not recommended
to implement complex logic in an application to handle this
automatically, as the user is already present and in control.