静态卡片

您可以使用简单的 REST API 插入、更新、读取和删除静态卡。此外,您还可以将对象附加到静态卡片,例如位置或媒体。

运作方式

默认情况下,静态卡片位于 Glass 时钟的右侧,并在递送时显示与用户相关的信息。不过,它们不需要像实时卡片那样立即予以关注,用户可以选择在空闲时阅读或执行操作。

当 Glassware 将静态卡片插入时间轴时,Glass 可能会播放通知提示音来提醒用户。之前的所有静态卡片也会向右移动,并在 7 天后或当新卡片数量超过 200 时从时间轴上消失。

适用情形

静态卡片非常适合在发生重要事件时向用户提供定期通知。例如,新闻推送服务会发送热门新闻报道。镜像 API 静态卡片还可以通过 OPEN_URI 菜单项启动实时卡片沉浸模式。这样,您就可以创建混合互动,将静态卡片用作通知,而将实时卡片或沉浸式体验结合起来打造更具互动性的体验。

如需查看时间轴项的可能操作的完整列表,请参阅参考文档

插入静态卡片

如需插入静态卡片(时间轴项),请将 时间轴项的 JSON 表示形式发布到 REST 端点。

时间轴项中的大多数字段都是可选的。最简单的形式是,时间轴项仅包含一条简短的短信,如以下示例所示:

原始 HTTP

POST /mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}
Content-Type: application/json
Content-Length: 26

{ "text": "Hello world" }

Java

TimelineItem timelineItem = new TimelineItem();
timelineItem.setText("Hello world");
service.timeline().insert(timelineItem).execute();

Python

timeline_item = {'text': 'Hello world'}
service.timeline().insert(body=timeline_item).execute()

成功后,您会收到 201 Created 响应代码,其中包含已创建项的完整副本。对于前面的示例,成功的响应可能如下所示:

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
 "kind": "glass#timelineItem",
 "id": "1234567890",
 "selfLink": "https://www.googleapis.com/mirror/v1/timeline/1234567890",
 "created": "2012-09-25T23:28:43.192Z",
 "updated": "2012-09-25T23:28:43.192Z",
 "etag": "\"G5BI0RWvj-0jWdBrdWrPZV7xPKw/t25selcGS3uDEVT6FB09hAG-QQ\"",
 "text": "Hello world"
}

插入式项目会显示在用户的时间轴中,如下所示:

插入带附件的时间轴项

一张图片胜过千言万语,远远超出一个时间轴项能够容纳的比例。为此,您还可以将图片和视频附加到时间轴项。以下示例展示了如何插入带照片附件的时间轴项:

原始 HTTP

POST /upload/mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}
Content-Type: multipart/related; boundary="mymultipartboundary"
Content-Length: {length}

--mymultipartboundary
Content-Type: application/json; charset=UTF-8

{ "text": "A solar eclipse of Saturn. Earth is also in this photo. Can you find it?" }
--mymultipartboundary
Content-Type: image/jpeg
Content-Transfer-Encoding: binary

[binary image data]
--mymultipartboundary--

Java

TimelineItem timelineItem = new TimelineItem();
timelineItem.setText("Hello world");
InputStreamContent mediaContent = new InputStreamContent(contentType, attachment);
service.timeline().insert(timelineItem, mediaContent).execute();

Python

timeline_item = {'text': 'Hello world'}
media_body = MediaIoBaseUpload(
    io.BytesIO(attachment), mimetype=content_type, resumable=True)
service.timeline().insert(body=timeline_item, media_body=media_body).execute()

附加了图片的时间轴项在 Glass 上如下所示:

附加视频

如果您要将视频文件附加到时间轴项,我们建议您流式传输视频,而不是一次性上传整个载荷。Google 镜像 API 支持通过 HTTP 实时流式传输、渐进式下载和实时流式传输协议 (RTSP) 进行流式传输。防火墙经常会阻止 RTSP,因此请尽可能使用其他选项。

如需流式传输视频,请使用 PLAY_VIDEO 内置菜单项,并将视频的网址指定为菜单项的 payload。如需了解详情,请参阅添加内置菜单项支持的媒体格式

分页

您可以对不适合放在单个时间轴卡片上但应以其他方式与同一卡片关联的时间轴项进行分页。分页项均共用相同的 timeline.id,因此具有一组相同的菜单项。当用户点按分页时间轴项时,系统会显示阅读更多内容菜单项。

Glass 会自动对显示 text 的时间轴项进行分页。如需让 Glass 自动对 html 进行分页,请使用 article 标记,并将其类属性设置为 auto-paginate,如以下示例所示:

<article class="auto-paginate">
 <h3>Very long list</h3>
 <ul>
   <li>First item</li>
   <li>Second item</li>
   <li>Third item</li>
   <li>Fourth item</li>
   <li>Fifth item</li>
   <li>Sixth item</li>
   <li>...</li>
 </ul>
<article>

如需手动分页,请对要在每张卡片上显示的内容使用 article 标记。Glass 会在单独的子时间轴卡片中显示每个 article 标记的内容。例如,您可以使用以下 HTML 创建分页时间轴项:

<article>
 <section>
   <p>First page</p>
 </section>
</article>

<article>
 <section>
   <p>Second page</p>
 </section>
</article>

<article>
 <section>
   <p>Third page</p>
 </section>
</article>

默认情况下,分页时间轴项的第一张卡片显示为封面卡片,当用户选择了解详情菜单项时,系统会再次显示此卡片。若想在点按了解详情后不再显示第一张卡片,您可以为第一个 <article> 标记指定 cover-only CSS 类:

<article class="cover-only">
...

cover-only 类还支持自动分页的时间轴项:

<article class="auto-paginate cover-only">
...

分类显示

借助捆绑功能,您可以将相关但互不相同的项归为一组,就像电子邮件会话中的单封邮件一样。媒体包有一个主封面卡片,用户点按该封面卡片即可显示包含套装中的其他卡片的子时间轴。媒体包与常规时间轴卡片的不同之处在于,后者会在封面/卡片的顶角的右上角折叠。

如需捆绑时间轴项,请为 bundleId 创建具有相同值的项。最近添加的就是该媒体包的封面卡片。

以下图片显示了套装封面卡片,右上角有折叠边,下方有两张套装卡片。

读取时间轴项

您的服务可以访问它创建的所有时间轴项,以及与其共享的所有时间轴项。这里介绍了如何列出服务可见的时间轴项。

原始 HTTP

GET /mirror/v1/timeline HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}

Java

TimelineItem timelineItem = new TimelineItem();
service.timeline().list().execute();

Python

service.timeline().list().execute()

您可以使用其他 REST 操作来获取更新删除时间轴项。

访问附件

您可以通过名为 attachments 的数组属性访问时间轴项的附件。然后,您可以通过附件的 contentUrl 属性或使用附件端点获取附件的二进制数据。

原始 HTTP

GET /mirror/v1/timeline/{itemId}/attachments/{attachmentId} HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer {auth token}

Java

TimelineItem item = service.timeline().get(itemId).execute();
String attachmentId = item.getAttachments().get(0).getId();
service.attachments().get(itemId, attachmentId).executeAsInputStream();

创建菜单项

菜单项可让用户请求与时间轴卡片相关的操作,分为两种类型:内置菜单项和自定义菜单项。

通过内置菜单项,您可以使用 Glass 提供的特殊功能,如朗读时间轴卡片、导航到某个位置、分享图片或回复消息:

自定义菜单项可让您的应用公开特定于 Glassware 的行为,您还可以提供与品牌相符的菜单项图标。

添加内置菜单项

您可以向时间轴项添加内置菜单项,方法是在插入菜单项时填充 menuItems array。如需使用内置菜单项,您只需要填充每个 menuItemaction

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
  "text": "Hello world",
  "menuItems": [
    {
      "action": "REPLY"
    }
  ]
}

定义自定义菜单项

如果内置菜单项不适合您,您可以在插入或更新时间轴项时执行以下操作,以创建自定义操作:

  • menuItem.action 指定 CUSTOM
  • 指定 menuItem.id。当用户点按自定义菜单项时,您的 Glassware 会收到填充了 menuItem.id 的通知。这样,您就可以确定通知的来源。
  • 指定 menuItem.values 以添加显示在 Glass 上的 iconUrldisplayName。指向一个 50 x 50 的 PNG 图片,图片是白色、透明的 iconUrl 背景。
  • 指定 displayTime。如果您未指定 displayTime,则每当用户点按自定义菜单项时,时间轴项就会移动到时间轴前面。

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
  "text": "Hello world",
  "displayTime": "2013-08-08T22:47:31-07:00",
  "menuItems": [
    {
      "action": "CUSTOM",
      "id": "complete"
      "values": [{
        "displayName": "Complete",
        "iconUrl": "http://example.com/icons/complete.png"
      }]
    }
  ]
}

允许用户固定您的时间轴卡片

您可以创建一个菜单项,让用户能够固定时间轴卡片,该时间轴卡片会在主时钟卡片的左侧永久显示。用户也可以使用同一菜单项取消固定卡片。

固定菜单项是内置菜单项,因此您只需为 menuItem 提供 TOGGLE_PINNED action 即可。

原始 HTTP

HTTP/1.1 201 Created
Date: Tue, 25 Sep 2012 23:30:11 GMT
Content-Type: application/json
Content-Length: 303

{
  "text": "You can pin or unpin this card.",
 "menuItems": [
    {
      "action": "TOGGLE_PINNED"
    }
  ...
 ]
}

订阅

借助 镜像 API,您可以订阅在用户对时间轴项执行特定操作或用户位置信息已更新时发送的通知。订阅通知时,您需要提供处理通知的回调网址。

接收通知

镜像 API 发出的通知以 POST 请求的形式发送到包含 JSON 请求正文的订阅端点。

原始 HTTP

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "UPDATE",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer",
  "userActions": [
    {
      "type": "<TYPE>",
      "payload": "<PAYLOAD>"
    }
  ]
}

Java

import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.services.mirror.model.Notification;

import java.io.IOException;
import java.io.InputStream;
// ...

public class MyClass {
  // ...

  /**
    * Parse a request body into a Notification object.
    *
    * @param requestBody The notification payload sent by the Mirror API.
    * @return Parsed notification payload if successful, {@code null} otherwise.
    */
  static Notification parseNotification(InputStream requestBody) {
    try {
      JsonFactory jsonFactory = new JacksonFactory();

      return jsonFactory.fromInputStream(requetBody, Notification.class);
    } catch (IOException e) {
      System.out.println("An error occurred: " + e);
      return null;
    }
  }

  // ...
}

Python

import json

def parse_notification(request_body):
  """Parse a request body into a notification dict.

  Params:
    request_body: The notification payload sent by the Mirror API as a string.
  Returns:
    Dict representing the notification payload.
  """
  return json.load(request_body)

如果没有出现错误,您的服务必须使用 200 OK HTTP 状态代码响应 API。如果您的服务返回错误代码,那么 镜像 API 可能会尝试重新向您的服务发送通知。

通知类型

镜像 API 为不同事件发送不同的通知载荷。

回复

用户已使用内置的 REPLY 菜单项回复了时间轴项:

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "INSERT",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer",
  "userActions": [
    {
      "type": "REPLY"
    }
  ]
}

itemId 属性设为包含以下内容的 ID

  • inReplyTo 属性设置为其所回复的时间轴项的 ID
  • text 属性设置为文本转录。
  • recipients 属性设置为其所回复的时间轴项的 creator(如果存在)。

例如:

{
  "kind": "glass#timelineItem",
  "id": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "inReplyTo": "3236e5b0-b282-4e00-9d7b-6b80e2f47f3d",
  "text": "This is a text reply",
  "recipients": [
    {
      "id": "CREATOR_ID",
      "displayName": "CREATOR_DISPLAY_NAME",
      "imageUrls": [
        "CREATOR_IMAGE_URL"
      ]
    }
  ]
}

删除

用户删除了时间轴项目:

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "DELETE",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer",
  "userActions": [
    {
      "type": "DELETE"
    }
  ]
}

itemId 属性设置为已删除项的 ID。除了 ID 和 isDeleted 属性之外,该项不再包含元数据。

已选择自定义菜单项

用户已选择您的服务设置的自定义菜单项

{
  "collection": "timeline",
  "itemId": "3hidvm0xez6r8_dacdb3103b8b604_h8rpllg",
  "operation": "UPDATE",
  "userToken": "harold_penguin",
  "userActions": [
    {
      "type": "CUSTOM",
      "payload": "PING"
    }
  ]
}

itemId 属性设为用户选择的菜单项的 ID。

userActions 数组包含用户对此项执行的自定义操作的列表。您的服务应相应地处理这些操作。

位置信息更新

当前用户可使用一个新位置:

{
  "collection": "locations",
  "itemId": "latest",
  "operation": "UPDATE",
  "userToken": "harold_penguin",
  "verifyToken": "random_hash_to_verify_referer"
}

当 Glassware 收到位置信息更新时,向 glass.locations.get 端点发送请求,以检索最新的已知位置。Glassware 每十分钟收到一次位置更新。

语音命令

用户激活了语音指令,例如:“Ok Glass,添加记事,Cat Stream,Chipotle 的生日是明天”。以下通知会发送到您的 Glass:

{
  "collection": "timeline",
  "operation": "INSERT",
  "userToken": "chipotle's_owner",
  "verifyToken": "mew mew mew",
  "itemId": "<ITEM_ID>",
  "userActions": [
    {“type”: "LAUNCH"}
  ]
}

此通知与 userActions 属性中的 LAUNCH 值不同。

然后,您可以使用 itemId 中的值来提取时间轴项:

{
  "id": "<ITEM_ID>",
  "text": "Chipotle's birthday is tomorrow",
  "recipients": [
    {"id": "CAT_STREAM"}
  ]
}

recipients 属性包含代表所用语音指令的联系人的 id