Pepper SDK入門(5) Robot focus と Robot lifecycle

Pepper SDK Plugin導入から始まり、簡単なロボアプリの開発を行ってきました。今回は、QiSDKの基本となるRobot focus と Robot lifecycleについて説明します。

#1.フォーカスを理解する
まずはロボットフォーカスについての簡単な説明です。 Activityがロボットのアクションを実行するためには、ロボットフォーカスが必要です。ロボットフォーカスはフォアグラウンドのActivityだけが保持することができ、ロボットフォーカスオーナーと呼ばれます。ロボットフォーカスはQiSDKに管理されており、Activityはいつでもロボットフォーカスを獲得したり喪失したりする可能性があります。

#2.ロボットライフサイクル
Pepper SDK入門(2) はじめてのロボアプリ開発①で登場したロボットライフサイクルについてです。 QiSDKがそれぞれのActivity用のロボットフォーカスを渡すために、RobotLifecycleCallbacksインタフェースを実装したオブジェクトが必要になります。以下はActivityにRobotLifecycleCallbacksインタフェースを実装した例です。

```java

public class MyActivity extends RobotActivity implements RobotLifecycleCallbacks
```

また、RobotLifecycleCallbacksオブジェクトにコールバックさせるにはonCreateメソッドでActivityとRobotLifecycleCallbacksオブジェクトをQiSDKに登録する必要があります。

```java

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    QiSDK.register(this, this);

}
```

その上で、onDestroyメソッドでの登録解除も必要です。 

```java

@Override
protected void onDestroy() {
    QiSDK.unregister(this, this);
    super.onDestroy();
}
```

#3.ロボットフォーカスの獲得 
ロボットフォーカスの獲得に関して詳述します。 ロボットフォーカスはActivityがフォアグラウンドに遷移したタイミングでActivityに与えられ、QiSDKからRobotLifecycleCallbacksオブジェクトのonRobotFocusGainedメソッドがコールバックされます。

```java

void onRobotFocusGained(QiContext qiContext);
```

**警告**
このコールバックはバックグラウンドスレッドで実行されているため、UIコンポーネントが内部で直接操作できません。UI thread上でコードを実行するには、下記の7.UI threadでの処理を参照してください。

以下が許可されているQiContextです。

- アクションの作成

```java

Say say = SayBuilder.with(qiContext)
                    .withText("こんにちは")
                    .build();
```

- アクション用リソースの作成

```java

Animation animation = AnimationBuilder.with(qiContext)
                                      .withResources(R.raw.elephant_a001)
                                      .build();
```

- ロボットサービスの取り扱い

```java

HumanAwareness humanAwareness = qiContext.getHumanAwareness();
```

このコールバックで、アクションを実行することが出来ます。
- アクションの実行

```java
say.run();
```

```java
say.async().run();
```

#4.ロボットフォーカスの喪失
ロボットフォーカス喪失について詳述します。 ロボットフォーカスを喪失したActivityと一緒に登録されたRobotLifecycleCallbacksオブジェクトのonRobotFocusLostメソッドがコールバックされます。

```java

void onRobotFocusLost();
```

**警告**
このコールバックはバックグラウンドスレッドで実行されているため、UIコンポーネントを内部で直接操作できません。UI thread上でコードを実行するには、下記の7.UI threadでの処理を参照してください。

**アクションの影響**
ロボットフォーカスを喪失した場合、このActivityはロボットフォーカスを再獲得するまでPepperのアクションを実行できません。 

```java

//ロボットフォーカスを喪失している場合、実行されません。
say.run();
```

また、アクションの実行中にonRobotFocusLostが呼ばれた場合、実行中のアクションは停止し例外がスローされます。 

```java

//例外がスローされます。
say.run();
```

```java

say.async().run().thenConsume(future -> {
    if (future.isSuccess()) {
//コールされません。
    } else if (future.hasError()) {
//コールされます。
    }
});
```

ロボットフォーカスなしでコールバックされるリスナーは、onRobotFocusLostメソッドで出来るだけ解除しておきましょう。

```java

if (lookAt != null) {
    lookAt.removeAllOnPolicyChangedListeners();
}
```

**ロボットサービスの影響**
ロボットサービスはロボットフォーカスの喪失に影響されず実行を継続します。

```java

Future<List<Human>> humansAroundFuture = humanAwareness.async().getHumansAround();
```

```java

humanAwareness.addOnHumansAroundChangedListener(listener);
```

ロボットフォーカスなしでコールバックされるリスナーは、onRobotFocusLostメソッドで出来るだけ解除しておきましょう。

```java

if (humanAwareness != null) {
    humanAwareness.removeAllOnHumansAroundChangedListeners();
}
```

ロボットサービスに設定したリスナーを解除し忘れた場合には、アプリが終了するまで実行されることになります。

#5. ロボットフォーカス拒否
ロボットフォーカス拒否について詳述します。 ロボットが不安定な時などは、Activityがフォアグラウンドに遷移したにもかかわらずロボットフォーカスが与えられない場合があります。その場合、このActivityと一緒に登録したRobotLifecycleCallbacksオブジェクトのonRobotFocusRefusedメソッドがコールバックされます。

```java

void onRobotFocusRefused(String reason);
```

ロボットフォーカスが与えられなかった原因は、onRobotFocusRefusedメソッドの引数で通知されます。

#6. 制約事項
制約事項についてです。 onRobotFocusGained、あるいはonRobotFocusRefusedのコールバックは保証されていません。onRobotFocusGainedやonRobotFocusRefusedを待ち続けることのないよう、必ずタイムアウトを実装しましょう。

#7.UIスレッドでの処理
UIスレッドでの処理に関して説明します。 Robot lifecycleの各メソッドで画面更新を行いたい場合には、2つの方法があります。

**Androidベースの解決策**
ActivityクラスのrunOnUiThreadメソッドにRunnableオブジェクトを渡すと、UIスレッドでの処理の実行が可能になります。

```java

@Override
public void onRobotFocusGained(QiContext qiContext) {

    Say say = SayBuilder.with(qiContext)
                        .withText("こんにちは")
                        .build();
   
    say.run();

    runOnUiThread(() -> textView.setText("完了"));
}
```

**QiSDKベースの解決策**
QiSDKクラスのonUiThreadメソッドにConsumerオブジェクトを渡すと、UIスレッドで処理を実行できるようになります。

```java

@Override
public void onRobotFocusGained(QiContext qiContext) {

    Say say = SayBuilder.with(qiContext)
                        .withText("こんにちは")
                        .build();

    Future<void> sayFuture = say.async().run();

    sayFuture.andThenConsume(Qi.onUiThread((Consumer<void>) ignore -> {
            textView.setText("完了");
    }));
}
```

#8.Activityライフサイクルとのリンク
Activityライフサイクルとロボットライフサイクルのリンクに関して説明します。

ロボットライフサイクルは完全にActivityライフサイクルとリンクしているわけではありません。しかしAとBの2つのActivityインスタンスがある時には、次のような条件でActivity Aがロボットフォーカスを喪失します。

- フォアグラウンドのActivityがAからBに遷移したとき
- アプリがバックグラウンドに遷移したとき
- アプリが終了させられたとき

**重要** 
Activityがバックグラウンドに遷移した場合は、必ずロボットフォーカスを失います。