Alexa APL, 第4回 デバイスごとの画面表示対応

#はじめに
今回はAlexaを搭載した画面付きデバイスごとの画面レイアウトを変更する方法を記載します。

##今回実施する内容
Echo Spot、Echo Show 5、Echo Show用に「画像」、「文字」のレイアウトをつける。
Echo Spotは形が丸なので、「画像」にオーバレイして「文字」のレイアウトにし、
Echo Show 5、Echo Showは、横並びに「画像」、「文字」のレイアウトにする。

中型デバイス（Echo Show用）
![中型デバイス用1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/54e15fb3-9e6e-edfb-acf6-463da7dea508.jpeg)

小型デバイス（Echo Show 5用）
![小型デバイス用１.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/50580c57-7d4b-0b76-1237-1873c5ef342c.jpeg)

小型デバイス（Echo Spot用）
![小型デバイスS2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/56a6415c-8e8d-f80c-4ea1-83eb5212018d.jpeg)


#環境
OS:Windows 10 JP
Alexaスキル言語：Node.js
Editor：Visual Studio Code
APLバージョン：1.0, 1.1

#参考
・[Alexa ハローAPL、Alexaスキルの画面への対応](https://qiita.com/Mount/items/72d9928ff2c0ae5de737)
第1回のAlexa APLの記事です。タイトル通り、ハローAPLを表示させるだけのAPLです。

・[APLトレーニングシリーズ第1回: 初めての Alexa Presentation Language (APL)](https://developer.amazon.com/ja/blogs/alexa/post/a3712152-cd02-4fda-9d33-3b64ef3a13af/jp-alexa-presentation-language-part1)
AmazonのAlexa Blogsに画面非対応Echoへの対応の記載がありました。

・[APLトレーニングシリーズ第2回: APLドキュメントの概要](https://developer.amazon.com/ja/blogs/alexa/post/0aa0bfac-044f-4c02-879c-92d8cb20768a/jp-alexa-presentation-language-part2)
AmazonのAlexa BlogsにAPLのレイアウト方法の記載がありました。

#用語
##APL
Alexa Presentation Language
amazonの画面つきのAlexaの画面表示用の言語。
JSONを使用した記載方法です。
インターネットのホームページはHTMLとCSSで作成しますが、AlexaはAPLで作成するということです。

##APLオーサリングツール
APL作成を視覚的に見ながらAPLのJSONファイルを作成するツール。
サンプルテンプレートも準備されており、その中から選択していくだけで、だいたいの画面は作成できる。

#前提条件
前提条件はとくにないといえばないですが、本まとめを読むにあたり、以下がわかっていることが前提です。
・alexa developer consoleのアカウントがある
・Alexaスキルを開発したことがある
・JSONの記載方法を知っている
・「Alexa ハローAPL、Alexaスキルの画面への対応」の記事をみている

#デバイスごとの画面表示対応
第1回で作成したAPLのJSONファイルから作成します。
今のところ、第3回までAPLのJSONファイルを更新していますが、わかりやすさのため、第1回目のシンプルなものに付け足していきます。

##画像と文字をレイアウトしてみる
Echo Show 5とEcho Show用に、横並びに「画像」、「文字」のレイアウトにしようと思います。こんな感じにしてみます。
中型デバイス（Echo Show）
![中型デバイス用1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/1ebdb594-b6f6-dafd-d8f4-2240a21e4d16.jpeg)

小型デバイス（Echo Show 5）
![小型デバイス用１.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/3529f1de-8e7c-4730-a43a-6b5ca364d3a4.jpeg)

小型デバイス（Echo Spot）
![小型デバイスS1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/2a947a8c-3ce5-12dd-fb94-920bd29e1ed8.jpeg)


「レイアウト画面」の設定はこんな感じにします。
![レイアウト1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/f8d23d7c-8f63-8f72-bdff-ca0c8c32cea1.jpeg)


1. APLオーサリングツールを起動して「最初から作成」を選択する。
2. 「レイアウト画面」の「mainTemplate」を選択し、「Container」を追加する。
3. 「レイアウト画面」で、「Container」を選択し、「詳細設定画面」で以下をそれぞれ設定する。

      ・alignItems: center
      ・direction: row
      ・paddingLeft: 5vw
4. 「レイアウト画面」の「Container」を選択し、「Image」を追加する。
5. 「Image」の「詳細設定画面」で以下をそれぞれ設定する。

      ・source：コーヒーの画像。400x480px。
      ・width：40vw
      ・height：80vh
      この値の計算は、以下のような根拠で出しました。
    中型デバイス（Echo Show）は、1024 x 600 px想定のため、
    widthの40vw：400/1024＝0.39≒0.4 ⇒ 画面の40%で40vw
    heightの80vh：480/600＝0.8 ⇒ 画面の80%で80vh
    デバイスに依存して、画面比率は変わりますので、ここでは中型デバイスを想定して作成しましたが、小型デバイスでは画面がおかしいので、それはこれから作成していきます。

6. 「レイアウト画面」の「Container」を選択し、「ScrollView」を追加する。「ScrollView」は、その中にあるTextやImageが画面に入りきらない場合スクロールできるようにするコンポーネントです。
7. 「ScrollView」の「詳細設定画面」で以下をそれぞれ設定する。

     ・width：45vw
     ・height：90vh
     ・spacing：5vw

8. 「レイアウト画面」の「ScrollView」を選択し、「Text」を追加する。
9. 「Text」の「詳細設定画面」で以下をそれぞれ設定する。

     ・text：${payload.sample.text}

8. 「JSONデータ」ボタンを押して、以下のJSONデータを記載する。

```json:JSONデータ
{
    "sample": {
        "text": "こんにちは。<br>この記事はAlexa Presentation Language（APL）の記載を学ぶための記事です。<br>それにしても左のコーヒーはおいしそうだな。最近はコーヒーカップでコーヒー飲むことがなくなりましたね。マグカップのほうがおおいですね。"
    }
}
```

##設定値説明
上記で設定した値の意味を図示します。
![設定値説明.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/7cb89f02-d9de-215e-0d3a-53dbee3f5b91.jpeg)

##デバイスごとの画面設定
小型デバイス(Echo Spot)や小型デバイス（Echo Show 5)など、ユーザーが使用するデバイスごとに画面サイズが異なると、上記で作成したAPLの画面レイアウトをみてわかるとおり、それに応じた最適な画面レイアウトが要求されます。Echo Spotは丸型ですから、このままでは画面レイアウトを変えないと見れたものではありません。
APLはデバイスごとにレイアウトを変える仕組みが実装されています。
この実現のための仕組みは、以下のようなイメージです。

・画面サイズなどのプロファイルの読み込み
　  ・Viewportプロファイルパッケージの読み込み
・デバイスごとに画面を作成
　  ・Containerを複数作成しデバイスごとの画面作成（whenにデバイスの識別を記載）（APLファイルはひとつ）
・画面サイズのプロファイルごとの画面表示選択
　　・スキル実行時に、デバイスを識別し、デバイスにあった画面を表示

###Viewportプロファイルパッケージの読み込み
デバイスの画面レイアウトなどの情報を保持したものがViewportプロファイルパッケージと呼ばれるもので、Amazonによって準備されています。
APLでは、このViewportプロファイルパッケージを読み込み（Import）、Viewportプロファイルごとの画面レイアウトを準備することで、デバイスに応じた画面レイアウトを実現します。
以下のコードでViewportプロファイルパッケージを読み込めます。

```json
    ...
    "import":[
        {
            "name": "alexa-viewport-profiles",
            "version" : "1.0.0"
        }
    ],
    ...
```

APLオーサリングツールでは、「Document」ボタンを押すと、JSONが表示されますが、「"import"」は空です。そこに上記を書き込みます。
現在のversionは、1.0.0ですが、APLもv1.1になったためそのうち更新されるかもしれませんが、今のところ1.0.0でEcho Show 5もサポートしているためまだ1.0.0で大丈夫なのかな。

###Containerを複数作成しデバイスごとの画面作成（whenにデバイスの識別を記載）（APLファイルはひとつ）（Echo Spot対応）
ここでは、小型デバイス（Echo Spot）むけの画面レイアウトを作成してみます。こんな感じです。よくある画像の上に文字を重ねるレイアウトです。
![小型デバイスS2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/2d94a6ee-bef8-23fe-de41-ec40b770c9f1.jpeg)

「レイアウト画面」の設定はこんな感じにします。
![レイアウト2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405480/0b475ecf-27a3-187b-9913-534c60fefdd3.jpeg)

1. 「レイアウト画面」の「mainTemplate」を選択し、「Container」を追加する。
2. 追加した「Container」の「詳細設定画面」で、以下を設定する。

      ・alignItems：center
      ・justifyContent：center
      ・when：${viewport.shape == 'round'}
      このwhenはviewportプロファイルごとに使用する「Container」をわけるための設定で、ここでは、`round`に設定することで、小型デバイス（Echo Spot）用にすることができます。
詳しくは、[Alexa Viewportプロファイルパッケージ](https://developer.amazon.com/ja/docs/alexa-presentation-language/apl-alexa-viewport-profiles-package.html)に記載があります。

3. 「レイアウト画面」の二つ目の「Container」をドラッグして、一つ目の「Container」の上に移動する。
     このレイアウトの順番は重要で、デバイスはAPLを読み込んで適用可能なAPLを上から精査して反映します。順番を入れ替えないと、最初に作成したレイアウトがEcho Spotに適用されてしまいます。だから順番を入れ替えます。
こうするとEcho Spotは最初に専用のAPLを読み込んで画像を表示しますし、その他のデバイス、例えばEcho Showは、whenの設定をみてEcho Show用のレイアウトではないと判断し、二つ目の「Container」でレイアウトするようになります。
4. ここからは入れ替えたEcho Spot用のContainerに設定を追加していきます。「レイアウト画面」の「Container」を選択して、「Image」を追加する。
5. 「Image」の「詳細設定画面」で以下をそれぞれ設定する。

      ・ source：コーヒーの画像。480x480px。
      ・ overlayColor：rgba(0,0,0, 0.6)
      ・ width：100vw
      ・ height：100vh
      ・ position：absolute

6. 「レイアウト画面」の「Container」を選択して、「ScrollView」を追加する。
7. 「レイアウト画面」の「ScrollView」を選択し、「詳細設定画面」で以下をそれぞれ設定する。

      ・width：90vw
      ・height：50vh
8. 「レイアウト画面」の「ScrollView」を選択し、「Text」を追加する。
9. 「レイアウト画面」の「Text」を選択し、「詳細設定画面」で以下をそれぞれ設定する。

     ・text: ${payload.sample.text}
     APLオーサリングツールでテキスト部分をみて、画面に入りきらないはずですが、スクロールできることを確認しておきます。

10. 「レイアウト画面」の「Text」を選択し、「詳細設定画面で以下を追加で設定する。
      ・fontSize：32dp
      文字が大きすぎるため少し小さくします。それでも画面には入りきりませんが。
      fontは、CSSのスタイルのように設定がありますが、今回の主旨ではないため、直接ここに記載しました。
      この辺りは別の機会にしたいと思います。

最後にAPLのソースコードを載せます。

```json:APL.json
{
    "document": {
        "type": "APL",
        "version": "1.1",
        "settings": {},
        "theme": "dark",
        "import": [
            {
                "name": "alexa-viewport-profiles",
                "version": "1.0.0"
            }
        ],
        "resources": [],
        "styles": {},
        "onMount": [],
        "graphics": {},
        "commands": {},
        "layouts": {},
        "mainTemplate": {
            "parameters": [
                "payload"
            ],
            "items": [
                {
                    "type": "Container",
                    "when": "${viewport.shape == 'round'}",
                    "alignItems": "center",
                    "justifyContent": "center",
                    "items": [
                        {
                            "type": "Image",
                            "width": "100vw",
                            "height": "100vh",
                            "source": "https://coffee_480x480.jpg",
                            "overlayColor": "rgba(0,0,0, 0.6)",
                            "position": "absolute"
                        },
                        {
                            "type": "ScrollView",
                            "width": "90vw",
                            "height": "50vh",
                            "items": [
                                {
                                    "type": "Text",
                                    "fontSize": "32dp",
                                    "text": "${payload.sample.text}"
                                }
                            ]
                        }
                    ]
                },
                {
                    "type": "Container",
                    "paddingLeft": "5vw",
                    "alignItems": "center",
                    "direction": "row",
                    "items": [
                        {
                            "type": "Image",
                            "width": "40vw",
                            "height": "80vh",
                            "source": "https://coffee_400x480.jpg"
                        },
                        {
                            "type": "ScrollView",
                            "width": "45vw",
                            "height": "90vh",
                            "items": [
                                {
                                    "type": "Text",
                                    "text": "${payload.sample.text}"
                                }
                            ],
                            "spacing": "5vw"
                        }
                    ]
                }
            ]
        }
    },
    "datasources": {
        "sample": {
            "text": "こんにちは。<br>この記事はAlexa Presentation Language（APL）の記載を学ぶための記事です。<br>それにしても左のコーヒーはおいしそうだな。最近はコーヒーカップでコーヒー飲むことがなくなりましたね。マグカップのほうがおおいですね。"
        }
    }
}
```

##Node.js側で画面表示を少し変えてみる
上記で作成したAPLを第2回に書いた方法で、documentとdatasourcesにわけて`index.js`に記載すれば、デバイスごとの画面の表示対応はできます。
APL側で画面表示対応をしましたが、Node.jsにおいても画面の違いを識別することができます。

###画面形状の取得
今回作成したテキストでは、「それにしても左のコーヒーはおいしそうだな。」と記載しましたが、Echo Spot用に作成したレイアウトでは、コーヒーは左にはなく、背景にしました。
ですので、Echo Spotの場合には、「それにしても**背景の**コーヒーはおいしそうだな。」と表示されるようにしたいと思います。

画面形状を知るには、`handlerInput.requestEnvelope.context.Viewport.shape`で判断できます。"ROUND"である場合、それは丸型のデバイスであることがわかります。
ここでは、丸型かどうかを判断して、変数positionの値を「左」、もしくは「背景」に変更しました。
ソースコードは以下の通りです。なお、`Util.isAPLSupported`関数は、第2回の記事に説明があります。

```js:index.js
const LaunchRequestHandler = {
  canHandle(handlerInput) {

    return handlerInput.requestEnvelope.request.type === 'LaunchRequest';
  },
  handle(handlerInput) {
    let position = "左";
    if (handlerInput.requestEnvelope.context.Viewport.shape === 'ROUND') {
      position = "背景";
    }
    const builder = handlerInput.responseBuilder
      .speak("画面表示へようこそ")
      .reprompt("画面表示へようこそ")
      .withStandardCard("画面", "画面表示へようこそ");

    if(Util.isAPLSupported(handlerInput.requestEnvelope)) {  
      builder.addDirective({
        type: 'Alexa.Presentation.APL.RenderDocument',
        version: '1.1',
        token: "token",
        document: require('./helloAPL.json'),
        datasources: {
            "sample": {
                "text": `こんにちは。<br>この記事はAlexa Presentation Language（APL）の記載を学ぶための記事です。<br>それにしても左のコーヒーはおいしそうだな。最近はコーヒーカップでコーヒー飲むことがなくなりましたね。マグカップのほうがおおいですね。`
            }
        }
      });
    } 
    return builder.getResponse();
  },
};
```

#おわりに
今回は、以下についてまとめました。
・デバイスごとの画面表示方法
・ScrollViewの設定方法
・Node.jsからデバイス形状を知る方法